File Coverage

blib/lib/Outthentic.pm
Criterion Covered Total %
statement 39 302 12.9
branch 0 170 0.0
condition 0 24 0.0
subroutine 13 31 41.9
pod n/a
total 52 527 9.8


line stmt bran cond sub pod time code
1             package Outthentic;
2              
3             our $VERSION = '0.3.17';
4              
5             1;
6              
7             package main;
8              
9 1     1   568 use Carp;
  1         1  
  1         44  
10 1     1   543 use Config::General;
  1         48957  
  1         82  
11 1     1   584 use YAML qw{LoadFile};
  1         6843  
  1         53  
12 1     1   631 use JSON;
  1         8710  
  1         8  
13 1     1   157 use Cwd;
  1         3  
  1         65  
14              
15 1     1   8 use strict;
  1         3  
  1         26  
16 1     1   595 use Data::Dumper;
  1         7460  
  1         88  
17 1     1   709 use File::Temp qw/ tempfile /;
  1         10467  
  1         65  
18 1     1   485 use Outthentic::Story;
  1         5  
  1         169  
19 1     1   660 use Term::ANSIColor;
  1         10020  
  1         76  
20 1     1   580 use Hash::Merge qw{merge};
  1         12043  
  1         67  
21 1     1   591 use Time::localtime;
  1         5200  
  1         62  
22 1     1   563 use Capture::Tiny;
  1         6871  
  1         4372  
23              
24             Hash::Merge::specify_behavior(
25             {
26             'SCALAR' => {
27             'SCALAR' => sub { $_[1] },
28             'ARRAY' => sub { [ $_[0], @{$_[1]} ] },
29             'HASH' => sub { $_[1] },
30             },
31             'ARRAY' => {
32             'SCALAR' => sub { $_[1] },
33             'ARRAY' => sub { [ @{$_[1]} ] },
34             'HASH' => sub { $_[1] },
35             },
36             'HASH' => {
37             'SCALAR' => sub { $_[1] },
38             'ARRAY' => sub { [ values %{$_[0]}, @{$_[1]} ] },
39             'HASH' => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
40             },
41             },
42             'Strun',
43             );
44              
45             my $config_data;
46              
47             our $STATUS = 1;
48              
49             sub execute_cmd {
50 0     0     my $cmd = shift;
51 0 0         note("execute cmd: $cmd") if debug_mod2();
52 0           (system($cmd) == 0);
53             }
54              
55             sub execute_cmd2 {
56              
57 0     0     my $cmd = shift;
58 0           my $out;
59              
60 0           my $format = get_prop('format');
61              
62 0 0         note("execute scenario: $cmd") if debug_mod2();
63              
64 0           my $stdout; my $stderr; my $exit;
  0            
65              
66 0 0         if ($format eq 'production'){
67 0     0     ( $stdout, $stderr, $exit) = Capture::Tiny::capture { system( $cmd ) };
  0            
68             } else{
69 0     0     ( $stdout, $stderr, $exit) = Capture::Tiny::tee { system( $cmd ) };
  0            
70             }
71              
72 0           return ($exit >> 8,$stdout.$stderr);
73             }
74              
75             sub config {
76 0     0     $config_data
77             }
78              
79             sub dump_config {
80 0     0     my $json = JSON->new->pretty;
81 0           print $json->encode(config());
82             }
83              
84             sub nocolor {
85 0     0     get_prop('nocolor')
86             }
87              
88             sub populate_config {
89              
90 0 0   0     unless (config()){
91 0 0 0       if (get_prop('ini_file_path') and -f get_prop('ini_file_path') ){
    0 0        
    0 0        
    0          
    0          
    0          
92 0           my $path = get_prop('ini_file_path');
93 0 0         my %c = Config::General->new(
94             -InterPolateVars => 1 ,
95             -InterPolateEnv => 1 ,
96             -ConfigFile => $path
97             )->getall or confess "file $path is not valid config file";
98 0           $config_data = {%c};
99             }elsif(get_prop('yaml_file_path') and -f get_prop('yaml_file_path')){
100 0           my $path = get_prop('yaml_file_path');
101 0           ($config_data) = LoadFile($path);
102             }elsif ( get_prop('json_file_path') and -f get_prop('json_file_path') ){
103 0           my $path = get_prop('json_file_path');
104 0 0         open DATA, $path or confess "can't open file $path to read: $!";
105 0           my $json_str = join "", ;
106 0           close DATA;
107 0           $config_data = from_json($json_str);
108             }elsif ( -f 'suite.ini' ){
109 0           my $path = 'suite.ini';
110 0 0         my %c = Config::General->new(
111             -InterPolateVars => 1 ,
112             -InterPolateEnv => 1 ,
113             -ConfigFile => $path
114             )->getall or confess "file $path is not valid config file";
115 0           $config_data = {%c};
116             }elsif ( -f 'suite.yaml'){
117 0           my $path = 'suite.yaml';
118 0           ($config_data) = LoadFile($path);
119             }elsif ( -f 'suite.json'){
120 0           my $path = 'suite.json';
121 0 0         open DATA, $path or confess "can't open file $path to read: $!";
122 0           my $json_str = join "", ;
123 0           close DATA;
124 0           $config_data = from_json($json_str);
125             }else{
126 0           $config_data = { };
127             }
128             }
129              
130 0           my $default_config;
131              
132 0 0         if ( -f 'suite.ini' ){
    0          
    0          
133 0           my $path = 'suite.ini';
134 0 0         my %c = Config::General->new(
135             -InterPolateVars => 1 ,
136             -InterPolateEnv => 1 ,
137             -ConfigFile => $path
138             )->getall or confess "file $path is not valid config file";
139 0           $default_config = {%c};
140             }elsif ( -f 'suite.yaml'){
141 0           my $path = 'suite.yaml';
142 0           ($default_config) = LoadFile($path);
143             }elsif ( -f 'suite.json'){
144 0           my $path = 'suite.json';
145 0 0         open DATA, $path or confess "can't open file $path to read: $!";
146 0           my $json_str = join "", ;
147 0           close DATA;
148 0           $default_config = from_json($json_str);
149             }else{
150 0           $default_config = { };
151             }
152              
153              
154 0           my @runtime_params;
155              
156 0 0         if (my $args_file = get_prop('args_file') ){
157 0 0         open ARGS_FILE, $args_file or die "can't open file $args_file to read: $!";
158 0           while (my $l = ) {
159 0           chomp $l;
160 0 0         next unless $l=~/\S/;
161 0           push @runtime_params, $l;
162             }
163 0           close ARGS_FILE;
164             } else {
165 0           @runtime_params = split /:::/, get_prop('runtime_params');
166             }
167              
168 0           my $config_res = merge( $default_config, $config_data );
169              
170 0           PARAM: for my $rp (@runtime_params){
171              
172 0           my $value;
173              
174 0 0         if ($rp=~s/=(.*)//){
175 0           $value = $1;
176             }else{
177 0           next PARAM;
178             }
179              
180 0           my @pathes = split /\./, $rp;
181 0           my $last_path = pop @pathes;
182              
183 0           my $root = $config_res;
184 0           for my $path (@pathes){
185 0 0         next PARAM unless defined $root->{$path};
186 0           $root = $root->{$path};
187             }
188 0           $root->{$last_path} = $value;
189             }
190              
191 0 0         open CONFIG, '>', story_cache_dir().'/config.json'
192             or die "can't open to write file ".story_cache_dir()."/config.json : $!";
193 0           my $json = JSON->new();
194 0           print CONFIG $json->encode($config_res);
195 0           close CONFIG;
196              
197 0 0         note("configuration populated and saved to ".story_cache_dir()."/config.json") if debug_mod12;
198              
199             # populating cli_args from config_data{args}
200 0 0         unless (get_prop('cli_args')){
201 0 0 0       if ($config_res->{'args'} and ref($config_res->{'args'}) eq 'ARRAY'){
202 0 0         note("populating cli args from args in configuration data") if debug_mod12;
203 0           my @cli_args;
204 0           for my $item (@{$config_res->{'args'}}){
  0            
205 0 0         if (! ref $item){
    0          
    0          
206 0           push @cli_args, $item;
207             } elsif(ref $item eq 'HASH'){
208 0           for my $k ( keys %{$item}){
  0            
209 0           my $k1 = $k;
210 0 0         if ($k1=~s/^~//){
211 0           push @cli_args, '-'.$k1, $item->{$k};
212             }else{
213 0           push @cli_args, '--'.$k1, $item->{$k};
214             }
215             }
216             } elsif(ref $item eq 'ARRAY'){
217             push @cli_args, map {
218 0           my $v = $_;
219 0 0         $v=~s/^~// ? '-'.$v : '--'.$v;
220 0           } @{$item};
  0            
221             };
222             }
223 0 0         note("cli args set to: ".(join ' ', @cli_args)) if debug_mod12;
224 0           set_prop('cli_args', join ' ', @cli_args );
225             }
226             }
227              
228 0 0         open CLI_ARGS, '>', story_cache_dir().'/cli_args'
229             or die "can't open to write file ".story_cache_dir()."/cli_args : $!";
230 0           print CLI_ARGS get_prop('cli_args');
231 0           close CLI_ARGS;
232              
233 0 0         note("cli args populated and saved to ".story_cache_dir()."/cli_args") if debug_mod12;
234              
235             # it should be done once
236             # and it always true
237             # as populate_config() reach this lines
238             # only once, when config is really populated
239              
240 0 0         if ( get_prop('cwd') ) {
241 0 0         unless (chdir(get_prop('cwd'))){
242 0           $STATUS = 0;
243 0           die "can't change working directory to: ".(get_prop('cwd'))." : $!";
244             }
245              
246             }
247            
248 0           return $config_data = $config_res;
249 0           return $config_data;
250             }
251              
252             sub print_story_header {
253              
254 0     0     my $task_name = get_prop('task_name');
255              
256 0           my $format = get_prop('format');
257 0           my $data;
258 0 0         if ($format eq 'production') {
    0          
259 0   0       $data = timestamp().' : '.($task_name || '').' '.(short_story_name($task_name))
260             } elsif ($format ne 'concise') {
261 0 0 0       $data = timestamp().' : '.($task_name || '' ).' '.(nocolor() ? short_story_name($task_name) : colored(['yellow'],short_story_name($task_name)))
262             }
263 0 0         if ($format eq 'production'){
264 0           note($data,1)
265             } else {
266 0           note($data)
267             }
268             }
269              
270             sub run_story_file {
271              
272 0 0   0     return get_prop('stdout') if defined get_prop('stdout');
273              
274 0           set_prop('has_scenario',1);
275              
276 0           my $format = get_prop('format');
277              
278 0           my $story_dir = get_prop('story_dir');
279              
280 0 0         if ( get_stdout() ){
281              
282              
283 0           print_story_header();
284              
285 0 0         note("stdout is already set") if debug_mod12;
286              
287 0 0         unless ($format eq 'production') {
288 0           for my $l (split /\n/, get_stdout()){
289 0           note($l);
290             }
291             }
292              
293 0           set_prop( stdout => get_stdout() );
294 0           set_prop( scenario_status => 1 );
295              
296 0           Outthentic::Story::Stat->set_scenario_status(1);
297 0           Outthentic::Story::Stat->set_stdout(get_stdout());
298              
299             } else {
300              
301              
302 0           my $story_command;
303              
304 0 0         if ( -f "$story_dir/story.pl" ){
    0          
    0          
    0          
305              
306 0 0         if (-f project_root_dir()."/cpanfile" ){
307 0           $story_command = "PATH=\$PATH:".project_root_dir()."/local/bin/ perl -I ".story_cache_dir().
308             " -I ".project_root_dir()."/local/lib/perl5 -I".project_root_dir()."/lib " ."-MOutthentic::Glue::Perl $story_dir/story.pl";
309             } else {
310 0           $story_command = "perl -I ".story_cache_dir()." -I ".project_root_dir()."/lib"." -MOutthentic::Glue::Perl $story_dir/story.pl";
311             }
312              
313 0           print_story_header();
314              
315             }elsif(-f "$story_dir/story.rb") {
316              
317 0           my $story_file = "$story_dir/story.rb";
318              
319 0           my $ruby_lib_dir = File::ShareDir::dist_dir('Outthentic');
320              
321 0 0         if (-f project_root_dir()."/Gemfile" ){
322 0           $story_command = "cd ".project_root_dir()." && bundle exec ruby -I $ruby_lib_dir -r outthentic -I ".story_cache_dir()." $story_file";
323             } else {
324 0           $story_command = "ruby -I $ruby_lib_dir -r outthentic -I ".story_cache_dir()." $story_file";
325             }
326              
327 0           print_story_header();
328              
329             }elsif(-f "$story_dir/story.py") {
330              
331 0           my $python_lib_dir = File::ShareDir::dist_dir('Outthentic');
332 0           $story_command = "PYTHONPATH=\$PYTHONPATH:".(story_cache_dir()).
333             ":$python_lib_dir python $story_dir/story.py";
334              
335 0           print_story_header();
336              
337             } elsif(-f "$story_dir/story.bash") {
338              
339 0           my $bash_lib_dir = File::ShareDir::dist_dir('Outthentic');
340 0           $story_command = "bash -c 'source ".story_cache_dir()."/glue.bash";
341 0           $story_command.= " && source $bash_lib_dir/outthentic.bash";
342 0           $story_command.= " && source $story_dir/story.bash'";
343              
344 0           print_story_header();
345              
346             } else {
347              
348             # print "empty story\n";
349              
350 0           return;
351             }
352              
353 0           my ($ex_code, $out) = execute_cmd2($story_command);
354              
355 0 0         print_story_messages($out) if $format eq 'production';
356              
357 0 0         if ($ex_code == 0) {
    0          
358 0 0         outh_ok(1, "scenario succeeded" ) unless $format eq 'production';
359 0           set_prop( scenario_status => 1 );
360 0           Outthentic::Story::Stat->set_scenario_status(1);
361 0           Outthentic::Story::Stat->set_stdout($out);
362              
363             }elsif(ignore_story_err()){
364 0           outh_ok(1, "scenario failed, still continue due to `ignore_story_err' is set");
365 0           set_prop( scenario_status => 2 );
366 0           Outthentic::Story::Stat->set_scenario_status(2);
367 0           Outthentic::Story::Stat->set_stdout($out);
368             }else{
369 0 0         if ( $format eq 'production'){
370 0           print "$out";
371 0           outh_ok(0, "scenario succeeded", $ex_code);
372             } else {
373 0           outh_ok(0, "scenario succeeded", $ex_code);
374             }
375 0           set_prop( scenario_status => 0 );
376 0           Outthentic::Story::Stat->set_scenario_status(0);
377 0           Outthentic::Story::Stat->set_stdout($out);
378 0           Outthentic::Story::Stat->set_status(0);
379             }
380              
381 0           set_prop( stdout => $out );
382              
383             }
384              
385              
386 0           return get_prop('stdout');
387             }
388              
389             sub header {
390              
391 0     0     my $project = project_root_dir();
392 0           my $story = get_prop('story');
393 0           my $story_type = get_prop('story_type');
394 0           my $story_file = get_prop('story_file');
395 0           my $debug = get_prop('debug');
396 0           my $ignore_story_err = ignore_story_err();
397            
398 0           note("project: $project");
399 0           note("story: $story");
400 0           note("story_type: $story_type");
401 0           note("debug: $debug");
402 0           note("ignore story errors: $ignore_story_err");
403              
404             }
405              
406             sub run_and_check {
407              
408 0     0     my $story_check_file = shift;
409              
410 0           my $format = get_prop('format');
411              
412 0 0         header() if debug_mod2();
413              
414 0           dsl()->{debug_mod} = get_prop('debug');
415              
416 0           dsl()->{match_l} = get_prop('match_l');
417              
418 0           eval { dsl()->{output} = run_story_file() };
  0            
419              
420            
421 0 0         if ($@) {
422 0           $STATUS = 0;
423 0           die "story run error: $@";
424             }
425              
426 0 0         return unless get_prop('scenario_status'); # we don't run checks for failed scenarios
427              
428 0 0         return unless $story_check_file;
429 0 0         return unless -s $story_check_file; # don't run check when check file is empty
430              
431 0           eval {
432 0 0         open my $fh, $story_check_file or confess $!;
433 0           my $check_list = join "", <$fh>; close $fh;
  0            
434 0           dsl()->validate($check_list)
435             };
436              
437 0           my $err = $@;
438 0           my $check_fail=0;
439 0           for my $r ( @{dsl()->results}){
  0            
440 0 0         note($r->{message}) if $r->{type} eq 'debug';
441 0 0         if ($r->{type} eq 'check_expression' ){
442 0           Outthentic::Story::Stat->add_check_stat($r);
443 0 0         $check_fail=1 unless $r->{status};
444 0 0         if ($format eq 'production'){
445 0 0         outh_ok($r->{status}, $r->{message}) unless $r->{status};
446             } else {
447 0           outh_ok($r->{status}, $r->{message});
448             }
449 0 0         Outthentic::Story::Stat->set_status(0) unless $r->{status};
450             };
451              
452             }
453              
454              
455 0 0         if ($err) {
456 0           $STATUS = 0;
457 0           die "validator error: $err";
458             }
459              
460 0 0 0       if ($format eq 'production' and $check_fail) {
461 0           print get_prop("stdout");
462             }
463             }
464              
465            
466             sub print_story_messages {
467 0     0     my $out = shift;
468 0 0         print " [msg] " if $out=~/outthentic_message/;
469 0           my @m = ($out=~/outthentic_message:\s+(.*)/g);
470 0           print join " ", @m;
471 0           print "\n";
472             }
473              
474             sub outh_ok {
475              
476 0     0     my $status = shift;
477 0           my $message = shift;
478 0           my $exit_code = shift;
479              
480 0           my $format = get_prop('format');
481              
482 0 0         if ($format ne 'concise'){
483 0 0         if ($status) {
484 0 0         print nocolor() ? "ok\t$message\n" : colored(['green'],"ok\t$message")."\n";
485             } else {
486 0 0         print nocolor() ? "not ok\t$message\n" : colored(['red'], "not ok\t$message")."\n";
487             }
488             }
489              
490 0 0 0       if ($status == 0 and $STATUS != 0 ){
491 0 0         $STATUS = ($exit_code == 1 ) ? -1 : 0;
492             }
493             }
494              
495             sub note {
496              
497 0     0     my $message = shift;
498 0           my $no_new_line = shift;
499              
500 0           binmode(STDOUT, ":utf8");
501 0           print $message;
502 0 0         print "\n" unless $no_new_line;
503              
504             }
505              
506              
507             sub print_meta {
508              
509 0 0   0     open META, get_prop('story_dir')."/meta.txt" or die $!;
510              
511 0           my $task_name = get_prop('task_name');
512              
513             #note( ( nocolor() ? short_story_name($task_name) : colored( ['yellow'], short_story_name($task_name) ) ));
514              
515 0           while (my $i = ){
516 0           chomp $i;
517 0           $i='@ '.$i;
518 0 0         note( nocolor() ? $i : colored( ['magenta'], "$i" ));
519             }
520 0           close META;
521              
522             }
523              
524             sub short_story_name {
525              
526 0     0     my $task_name = shift;
527              
528 0           my $story_dir = get_prop('story_dir');
529              
530 0           my $cwd_size = scalar(split /\//, get_prop('project_root_dir'));
531              
532 0           my $short_story_dir;
533              
534             my $i;
535              
536 0           for my $l (split /\//, $story_dir){
537 0 0         $short_story_dir.=$l."/" unless $i++ < $cwd_size;
538              
539             }
540              
541 0           my $story_vars = story_vars_pretty();
542              
543 0   0       $short_story_dir ||= "/";
544              
545 0           my @ret;
546              
547 0 0         push @ret, "[path] $short_story_dir" if $short_story_dir;
548 0 0         push @ret, "[params] $story_vars" if $story_vars;
549              
550 0           join " ", @ret;
551              
552             }
553              
554             sub timestamp {
555 0     0     sprintf '%02d-%02d-%02d %02d:%02d:%02d',
556             localtime->year()+1900,
557             localtime->mon()+1, localtime->mday,
558             localtime->hour, localtime->min, localtime->sec;
559             }
560              
561             END {
562              
563             #print "STATUS: $STATUS\n";
564              
565             if ($STATUS == 1){
566             exit(0);
567             } elsif($STATUS == -1){
568             exit(1);
569             } else{
570             exit(2);
571             }
572              
573            
574             }
575              
576             1;
577              
578              
579             __END__