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 2     2   159748 use v5.14;
  2         18  
2 2     2   11 use warnings;
  2         9  
  2         102  
3              
4             package Test::BDD::Cucumber::Harness::JSON 0.85;
5              
6             =head1 NAME
7              
8             Test::BDD::Cucumber::Harness::JSON - Generate results to JSON file
9              
10             =head1 VERSION
11              
12             version 0.85
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 2     2   621 use Moo;
  2         12016  
  2         10  
24 2     2   2516 use Types::Standard qw( Num HashRef ArrayRef FileHandle );
  2         75709  
  2         13  
25 2     2   2088 use JSON::MaybeXS;
  2         4  
  2         149  
26 2     2   13 use Time::HiRes qw ( time );
  2         21  
  2         20  
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 4     4 1 23 my ( $self, $feature ) = @_;
61 4         28 $self->current_feature( $self->format_feature($feature) );
62 4         109 push @{ $self->all_features }, $self->current_feature;
  4         86  
63             }
64              
65             sub scenario {
66 18     18 1 56 my ( $self, $scenario, $dataset ) = @_;
67 18         74 $self->current_scenario( $self->format_scenario($scenario) );
68 18         956 push @{ $self->current_feature->{elements} }, $self->current_scenario;
  18         335  
69             }
70              
71             sub scenario_done {
72 18     18 1 32 my $self = shift;
73 18         344 $self->current_scenario( {} );
74             }
75              
76             sub step {
77 68     68 1 128 my ( $self, $context ) = @_;
78 68         1382 $self->step_start_at( time() );
79             }
80              
81             sub step_done {
82 68     68 1 140 my ( $self, $context, $result ) = @_;
83 68         1599 my $duration = time() - $self->step_start_at;
84 68         596 my $step_data = $self->format_step( $context, $result, $duration );
85 68         141 push @{ $self->current_scenario->{steps} }, $step_data;
  68         1175  
86             }
87              
88             sub shutdown {
89 2     2 1 168 my ($self) = @_;
90 2         5 my $json = JSON::MaybeXS->new( %{ $self->json_args } );
  2         34  
91 2         111 my $fh = $self->fh;
92 2         497 print $fh $json->encode( $self->all_features );
93             }
94              
95             ##################################
96             ### Internal formating methods ###
97             ##################################
98              
99             sub format_tags {
100 22     22 0 181 my ( $self, $tags_ref ) = @_;
101 22         221 return [ map { { name => '@' . $_ } } @$tags_ref ];
  22         298  
102             }
103              
104             sub format_description {
105 22     22 0 2344 my ( $self, $description ) = @_;
106 22         51 return join "\n", map { $_->content } @{ $description };
  6         20  
  22         381  
107             }
108              
109             sub format_feature {
110 4     4 0 13 my ( $self, $feature ) = @_;
111             return {
112 4         81 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 18     18 0 40 my ( $self, $scenario, $dataset ) = @_;
125             return {
126 18 50       374 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 22     22   733 my ( $self, $line ) = @_;
139 22         101 return $line->filename . ":" . $line->number;
140             }
141              
142             sub format_step {
143 68     68 0 160 my ( $self, $step_context, $result, $duration ) = @_;
144 68         155 my $step = $step_context->step;
145             return {
146 68 50       1164 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 68     68 0 3059 my ( $self, $result, $duration ) = @_;
162 68 50       162 return { status => "undefined" } if not $result;
163             return {
164 68 50       658 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;