File Coverage

lib/Test/BDD/Cucumber/Harness/JSON.pm
Criterion Covered Total %
statement 58 58 100.0
branch 5 10 50.0
condition n/a
subroutine 19 19 100.0
pod 6 12 50.0
total 88 99 88.8


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