File Coverage

lib/Test/BDD/Cucumber/Harness/JSON.pm
Criterion Covered Total %
statement 53 53 100.0
branch 5 10 50.0
condition n/a
subroutine 17 17 100.0
pod 6 12 50.0
total 81 92 88.0


line stmt bran cond sub pod time code
1             package Test::BDD::Cucumber::Harness::JSON;
2             $Test::BDD::Cucumber::Harness::JSON::VERSION = '0.84';
3             =head1 NAME
4              
5             Test::BDD::Cucumber::Harness::JSON - Generate results to JSON file
6              
7             =head1 VERSION
8              
9             version 0.84
10              
11             =head1 DESCRIPTION
12              
13             A L subclass that generates JSON output file.
14              
15             So that it is possible use tools like
16             L<"Publish pretty cucumber reports"|https://github.com/masterthought/cucumber-reporting>.
17              
18             =cut
19              
20 2     2   161960 use Moo;
  2         12346  
  2         11  
21 2     2   2475 use Types::Standard qw( Num HashRef ArrayRef FileHandle );
  2         78113  
  2         19  
22 2     2   2286 use JSON::MaybeXS;
  2         7  
  2         165  
23 2     2   15 use Time::HiRes qw ( time );
  2         6  
  2         31  
24              
25             extends 'Test::BDD::Cucumber::Harness::Data';
26              
27             =head1 CONFIGURABLE ATTRIBUTES
28              
29             =head2 fh
30              
31             A filehandle to write output to; defaults to C
32              
33             =cut
34              
35             has 'fh' => ( is => 'rw', isa => FileHandle, default => sub { \*STDOUT } );
36              
37             =head2 json_args
38              
39             List of options to be passed to L's C method
40              
41             =cut
42              
43             has json_args => (
44             is => 'ro',
45             isa => HashRef,
46             default => sub { { utf8 => 1, pretty => 1 } }
47             );
48              
49             #
50              
51             has all_features => ( is => 'ro', isa => ArrayRef, default => sub { [] } );
52             has current_feature => ( is => 'rw', isa => HashRef );
53             has current_scenario => ( is => 'rw', isa => HashRef );
54             has step_start_at => ( is => 'rw', isa => Num );
55              
56             sub feature {
57 4     4 1 12 my ( $self, $feature ) = @_;
58 4         20 $self->current_feature( $self->format_feature($feature) );
59 4         129 push @{ $self->all_features }, $self->current_feature;
  4         77  
60             }
61              
62             sub scenario {
63 18     18 1 64 my ( $self, $scenario, $dataset ) = @_;
64 18         88 $self->current_scenario( $self->format_scenario($scenario) );
65 18         958 push @{ $self->current_feature->{elements} }, $self->current_scenario;
  18         362  
66             }
67              
68             sub scenario_done {
69 18     18 1 41 my $self = shift;
70 18         359 $self->current_scenario( {} );
71             }
72              
73             sub step {
74 68     68 1 144 my ( $self, $context ) = @_;
75 68         1379 $self->step_start_at( time() );
76             }
77              
78             sub step_done {
79 68     68 1 147 my ( $self, $context, $result ) = @_;
80 68         1623 my $duration = time() - $self->step_start_at;
81 68         598 my $step_data = $self->format_step( $context, $result, $duration );
82 68         127 push @{ $self->current_scenario->{steps} }, $step_data;
  68         1203  
83             }
84              
85             sub shutdown {
86 2     2 1 159 my ($self) = @_;
87 2         6 my $json = JSON::MaybeXS->new( %{ $self->json_args } );
  2         32  
88 2         102 my $fh = $self->fh;
89 2         782 print $fh $json->encode( $self->all_features );
90             }
91              
92             ##################################
93             ### Internal formating methods ###
94             ##################################
95              
96             sub format_tags {
97 22     22 0 217 my ( $self, $tags_ref ) = @_;
98 22         224 return [ map { { name => '@' . $_ } } @$tags_ref ];
  22         306  
99             }
100              
101             sub format_description {
102 22     22 0 2596 my ( $self, $description ) = @_;
103 22         42 return join "\n", map { $_->content } @{ $description };
  6         17  
  22         432  
104             }
105              
106             sub format_feature {
107 4     4 0 11 my ( $self, $feature ) = @_;
108             return {
109 4         86 uri => $feature->name_line->filename,
110             keyword => $feature->keyword_original,
111             id => $self->_generate_stable_id( $feature->name_line ),
112             name => $feature->name,
113             line => $feature->name_line->number,
114             description => $self->format_description($feature->satisfaction),
115             tags => $self->format_tags( $feature->tags ),
116             elements => []
117             };
118             }
119              
120             sub format_scenario {
121 18     18 0 61 my ( $self, $scenario, $dataset ) = @_;
122             return {
123 18 50       380 keyword => $scenario->keyword_original,
124             id => $self->_generate_stable_id( $scenario->line ),
125             name => $scenario->name,
126             line => $scenario->line->number,
127             description => $self->format_description($scenario->description),
128             tags => $self->format_tags( $scenario->tags ),
129             type => $scenario->background ? 'background' : 'scenario',
130             steps => []
131             };
132             }
133              
134             sub _generate_stable_id {
135 22     22   805 my ( $self, $line ) = @_;
136 22         89 return $line->filename . ":" . $line->number;
137             }
138              
139             sub format_step {
140 68     68 0 146 my ( $self, $step_context, $result, $duration ) = @_;
141 68         195 my $step = $step_context->step;
142             return {
143 68 50       1245 keyword => $step ? $step->verb_original : $step_context->verb,
    50          
144             name => $step_context->text,
145             line => $step ? $step->line->number : 0,
146             result => $self->format_result( $result, $duration )
147             };
148             }
149              
150             my %OUTPUT_STATUS = (
151             passing => 'passed',
152             failing => 'failed',
153             pending => 'pending',
154             undefined => 'skipped',
155             );
156              
157             sub format_result {
158 68     68 0 3395 my ( $self, $result, $duration ) = @_;
159 68 50       157 return { status => "undefined" } if not $result;
160             return {
161 68 50       641 status => $OUTPUT_STATUS{ $result->result },
162             error_message => $result->output,
163             defined $duration
164             ? ( duration => int( $duration * 1_000_000_000 ) )
165             : (), # nanoseconds
166             };
167             }
168              
169             =head1 SEE ALSO
170              
171             L
172              
173             L
174              
175             L
176              
177             =cut
178              
179             1;