File Coverage

blib/lib/Assert/Refute/T/Hash.pm
Criterion Covered Total %
statement 46 46 100.0
branch 12 12 100.0
condition 4 7 57.1
subroutine 10 10 100.0
pod n/a
total 72 75 96.0


line stmt bran cond sub pod time code
1             package Assert::Refute::T::Hash;
2              
3 3     3   1368 use strict;
  3         7  
  3         84  
4 3     3   15 use warnings;
  3         9  
  3         116  
5             our $VERSION = '0.1501';
6              
7             =head1 NAME
8              
9             Assert::Refute::T::Hash - Assertions about hashes for Assert::Refute suite
10              
11             =head1 SYNOPSIS
12              
13             use Test::More;
14             use Assert::Refute::T::Hash;
15              
16             keys_are { foo => 42, bar => 137 }, ["foo"], ["bar"], "Hash keys as expected";
17              
18             =head1 EXPORTS
19              
20             All of the below functions are exported by default:
21              
22             =cut
23              
24 3     3   16 use Carp;
  3         7  
  3         154  
25 3     3   27 use Scalar::Util qw(blessed);
  3         6  
  3         160  
26 3     3   23 use parent qw(Exporter);
  3         8  
  3         16  
27             our @EXPORT = qw(values_are);
28              
29 3     3   214 use Assert::Refute::Build;
  3         6  
  3         219  
30 3     3   21 use Assert::Refute qw(:all); # TODO Assert::Refute::Contract please
  3         13  
  3         24  
31              
32             =head2 keys_are \%hash, \@required, \@allowed, "Message"
33              
34             Check that keys in hash are exactly as expected:
35              
36             =over
37              
38             =item * if \@required is present, make sure that all keys listed there exist;
39              
40             =item * if \@allowed is present, make sure no keys are present
41             except those listed in either required or allowed.
42              
43             =back
44              
45             =cut
46              
47             build_refute keys_are => sub {
48 6     6   13 my ($hash, $required, $allowed) = @_;
49              
50 6   50     12 $required ||= [];
51              
52 6         11 my @missing = grep { !exists $hash->{$_} } @$required;
  6         16  
53 6         9 my @extra;
54 6 100       13 if ($allowed) {
55 3         4 my %seen;
56 3         10 $seen{$_}++ for @$required, @$allowed;
57 3         8 @extra = grep { !exists $seen{$_} } keys %$hash;
  6         15  
58             };
59              
60 6         9 my @msg;
61 6 100       17 push @msg, "Required keys missing (@missing)" if @missing;
62 6 100       18 push @msg, "Unexpected keys present (@extra)" if @extra;
63 6         24 return join "; ", @msg;
64             }, args => 3, export => 1;
65              
66             =head2 values_are \%hash, \%spec
67              
68             For each key in %spec, check corresponding value in %hash:
69              
70             =over
71              
72             =item * if spec is C, only accept undefined or missing value;
73              
74             =item * if spec is a string or number, check exact match (C);
75              
76             =item * if spec is a regular expression, apply it (C);
77              
78             =item * if spec is a contract or sub, apply it to the value (C);
79              
80             =back
81              
82             B<[NOTE]> This test should die if any other value appears in the spec.
83             However, it does not yet, instead producing a warning and
84             an unconditionally failed test.
85              
86             =cut
87              
88             build_refute values_are => sub {
89 4     4   16 my ($self, $hash, $spec, $message) = @_;
90              
91 4   50     11 $message ||= "hash values as expected";
92             $self->subcontract( $message => sub {
93 4     4   17 foreach ( keys %$spec ) {
94 8         29 my $cond = $spec->{$_};
95 8 100 66     52 if (!ref $cond) {
    100          
    100          
96 4         30 is $hash->{$_}, $cond, "$_ exact value";
97             } elsif (ref $cond eq 'Regexp') {
98 1         24 like $hash->{$_}, $cond, "$_ regex";
99             } elsif (blessed $cond or UNIVERSAL::isa($cond, 'CODE')) {
100 2         12 subcontract "$_ contract" => $cond, $hash->{$_};
101             } else {
102             # TODO bail_out when we can
103 1         271 carp "FIX TEST! Unexpected value in spec: '$_'=". ref $cond;
104 1         142 croak "FIX TEST! Unexpected value in spec: '$_'=". ref $cond;
105             };
106             };
107 4         27 });
108             }, manual => 1, args => 2, export => 1;
109              
110             =head1 LICENSE AND COPYRIGHT
111              
112             This module is part of L suite.
113              
114             Copyright 2017-2018 Konstantin S. Uvarin. C<< >>
115              
116             This program is free software; you can redistribute it and/or modify it
117             under the terms of the the Artistic License (2.0). You may obtain a
118             copy of the full license at:
119              
120             L
121              
122             =cut
123              
124             1;