File Coverage

blib/lib/Outthentic.pm
Criterion Covered Total %
statement 39 310 12.5
branch 0 180 0.0
condition 0 25 0.0
subroutine 13 31 41.9
pod n/a
total 52 546 9.5


line stmt bran cond sub pod time code
1             package Outthentic;
2              
3             our $VERSION = '0.4.7';
4              
5             1;
6              
7             package main;
8              
9 1     1   871 use Carp;
  1         2  
  1         60  
10 1     1   773 use Config::General;
  1         24472  
  1         55  
11 1     1   470 use YAML qw{LoadFile};
  1         7109  
  1         54  
12 1     1   682 use JSON;
  1         8255  
  1         6  
13 1     1   123 use Cwd;
  1         2  
  1         51  
14              
15 1     1   6 use strict;
  1         2  
  1         18  
16 1     1   640 use Data::Dumper;
  1         6339  
  1         65  
17 1     1   716 use File::Temp qw/ tempfile /;
  1         8958  
  1         61  
18 1     1   512 use Outthentic::Story;
  1         3  
  1         133  
19 1     1   8 use Term::ANSIColor;
  1         2  
  1         45  
20 1     1   561 use Hash::Merge qw{merge};
  1         11070  
  1         57  
21 1     1   8 use Time::localtime;
  1         2  
  1         50  
22 1     1   554 use Capture::Tiny;
  1         6640  
  1         4057  
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)
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 0           print_story_header();
283              
284 0 0         note("stdout is already set") if debug_mod12;
285              
286 0 0         unless ($format eq 'production') {
287 0           for my $l (split /\n/, get_stdout()){
288 0           note($l);
289             }
290             }
291              
292 0           set_prop( stdout => get_stdout() );
293 0           set_prop( scenario_status => 1 );
294              
295 0           Outthentic::Story::Stat->set_scenario_status(1);
296 0           Outthentic::Story::Stat->set_stdout(get_stdout());
297              
298             } else {
299              
300              
301 0           my $story_command;
302              
303 0 0         if ( -f "$story_dir/story.pl" ){
    0          
    0          
    0          
    0          
304              
305 0 0         if (-f project_root_dir()."/cpanfile" ){
306 0 0         if ( $^O =~ 'MSWin' ){
307 0           $story_command = "set 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 = "PATH=\$PATH:".project_root_dir()."/local/bin/ perl -I ".story_cache_dir().
311             " -I ".project_root_dir()."/local/lib/perl5 -I".project_root_dir()."/lib " ."-MOutthentic::Glue::Perl $story_dir/story.pl";
312             }
313             } else {
314 0           $story_command = "perl -I ".story_cache_dir()." -I ".project_root_dir()."/lib"." -MOutthentic::Glue::Perl $story_dir/story.pl";
315             }
316              
317 0           print_story_header();
318              
319             } elsif(-f "$story_dir/story.rb") {
320              
321 0           my $story_file = "$story_dir/story.rb";
322              
323 0           my $ruby_lib_dir = File::ShareDir::dist_dir('Outthentic');
324              
325 0 0         if (-f project_root_dir()."/Gemfile" ){
326 0           $story_command = "cd ".project_root_dir()." && bundle exec ruby -I $ruby_lib_dir -r outthentic -I ".story_cache_dir()." $story_file";
327             } else {
328 0           $story_command = "ruby -I $ruby_lib_dir -r outthentic -I ".story_cache_dir()." $story_file";
329             }
330              
331 0           print_story_header();
332              
333             } elsif(-f "$story_dir/story.py") {
334              
335 0           my $python_lib_dir = File::ShareDir::dist_dir('Outthentic');
336 0           $story_command = "PYTHONPATH=\$PYTHONPATH:".(story_cache_dir()).
337             ":$python_lib_dir python $story_dir/story.py";
338              
339 0           print_story_header();
340              
341             } elsif(-f "$story_dir/story.bash") {
342              
343 0           my $bash_lib_dir = File::ShareDir::dist_dir('Outthentic');
344 0           $story_command = "bash -c 'source ".story_cache_dir()."/glue.bash";
345 0           $story_command.= " && source ".$bash_lib_dir."/outthentic.bash";
346 0           $story_command.= " && source $story_dir/story.bash'";
347              
348 0           print_story_header();
349              
350             } elsif(-f "$story_dir/story.ps1") {
351              
352 0           my $ps_lib_dir = File::ShareDir::dist_dir('Outthentic');
353              
354 0 0         if ( $^O =~ 'MSWin' ){
355 0           $story_command = "powershell.exe -NoProfile -c \". ".story_cache_dir()."/glue.ps1; . $ps_lib_dir/outthentic.ps1; . $story_dir/story.ps1\""
356             } else {
357 0           $story_command = "pwsh -c \". ".story_cache_dir()."/glue.ps1; . $ps_lib_dir/outthentic.ps1; . $story_dir/story.ps1\"";
358             }
359              
360 0           print_story_header();
361              
362             } else {
363              
364             # print "empty story\n";
365              
366 0           return;
367             }
368              
369 0 0         print "run story: $story_command ...\n" if debug_mod12;
370              
371 0           my ($ex_code, $out) = execute_cmd2($story_command);
372              
373 0 0         print_story_messages($out) if $format eq 'production';
374              
375 0 0         if ($ex_code == 0) {
    0          
376 0 0         outh_ok(1, "scenario succeeded" ) unless $format eq 'production';
377 0           set_prop( scenario_status => 1 );
378 0           Outthentic::Story::Stat->set_scenario_status(1);
379 0           Outthentic::Story::Stat->set_stdout($out);
380              
381             }elsif(ignore_story_err()){
382 0           outh_ok(1, "scenario failed, still continue due to `ignore_story_err' is set");
383 0           set_prop( scenario_status => 2 );
384 0           Outthentic::Story::Stat->set_scenario_status(2);
385 0           Outthentic::Story::Stat->set_stdout($out);
386             }else{
387 0 0         if ( $format eq 'production'){
388 0           print "$out";
389 0           outh_ok(0, "scenario succeeded", $ex_code);
390             } else {
391 0           outh_ok(0, "scenario succeeded", $ex_code);
392             }
393 0           set_prop( scenario_status => 0 );
394 0           Outthentic::Story::Stat->set_scenario_status(0);
395 0           Outthentic::Story::Stat->set_stdout($out);
396 0           Outthentic::Story::Stat->set_status(0);
397             }
398              
399 0           set_prop( stdout => $out );
400              
401             }
402              
403              
404 0           return get_prop('stdout');
405             }
406              
407             sub header {
408              
409 0     0     my $project = project_root_dir();
410 0           my $story = get_prop('story');
411 0           my $story_type = get_prop('story_type');
412 0           my $story_file = get_prop('story_file');
413 0           my $debug = get_prop('debug');
414 0           my $ignore_story_err = ignore_story_err();
415            
416 0           note("project: $project");
417 0           note("story: $story");
418 0           note("story_type: $story_type");
419 0           note("debug: $debug");
420 0           note("ignore story errors: $ignore_story_err");
421              
422             }
423              
424             sub run_and_check {
425              
426 0     0     my $story_check_file = shift;
427              
428 0           my $format = get_prop('format');
429              
430 0 0         header() if debug_mod2();
431              
432 0           dsl()->{debug_mod} = get_prop('debug');
433              
434 0           dsl()->{match_l} = get_prop('match_l');
435              
436 0           eval { dsl()->{output} = run_story_file() };
  0            
437              
438            
439 0 0         if ($@) {
440 0           $STATUS = 0;
441 0           die "story run error: $@";
442             }
443              
444 0 0         return unless get_prop('scenario_status'); # we don't run checks for failed scenarios
445              
446 0 0         return unless $story_check_file;
447 0 0         return unless -s $story_check_file; # don't run check when check file is empty
448              
449 0           eval {
450 0 0         open my $fh, $story_check_file or confess $!;
451 0           my $check_list = join "", <$fh>; close $fh;
  0            
452 0           dsl()->validate($check_list)
453             };
454              
455 0           my $err = $@;
456 0           my $check_fail=0;
457 0           for my $r ( @{dsl()->results}){
  0            
458 0 0         note($r->{message}) if $r->{type} eq 'debug';
459 0 0         if ($r->{type} eq 'check_expression' ){
460 0           Outthentic::Story::Stat->add_check_stat($r);
461 0 0         $check_fail=1 unless $r->{status};
462 0 0         if ($format eq 'production'){
463 0 0         outh_ok($r->{status}, $r->{message}) unless $r->{status};
464             } else {
465 0           outh_ok($r->{status}, $r->{message});
466             }
467 0 0         Outthentic::Story::Stat->set_status(0) unless $r->{status};
468             };
469              
470             }
471              
472              
473 0 0         if ($err) {
474 0           $STATUS = 0;
475 0           die "validator error: $err";
476             }
477              
478 0 0 0       if ($format eq 'production' and $check_fail) {
479 0           print get_prop("stdout");
480             }
481             }
482              
483            
484             sub print_story_messages {
485 0     0     my $out = shift;
486 0           my @m = ($out=~/outthentic_message:\s+(.*)/g);
487 0           for my $m (@m) {
488 0           chomp $m;
489 0           print "[msg] $m\n";
490             }
491             }
492              
493             sub outh_ok {
494              
495 0     0     my $status = shift;
496 0           my $message = shift;
497 0           my $exit_code = shift;
498              
499 0           my $format = get_prop('format');
500              
501 0 0         if ($format ne 'concise'){
502 0 0         if ($status) {
503 0 0         print nocolor() ? "ok\t$message\n" : colored(['green'],"ok\t$message")."\n";
504             } else {
505 0 0         print nocolor() ? "not ok\t$message\n" : colored(['red'], "not ok\t$message")."\n";
506             }
507             }
508              
509 0 0 0       if ($status == 0 and $STATUS != 0 ){
510 0 0         $STATUS = ($exit_code == 1 ) ? -1 : 0;
511             }
512             }
513              
514             sub note {
515              
516 0     0     my $message = shift;
517 0           my $no_new_line = shift;
518              
519 0           binmode(STDOUT, ":utf8");
520 0           print $message;
521 0 0         print "\n" unless $no_new_line;
522              
523             }
524              
525              
526             sub print_meta {
527              
528 0 0   0     open META, get_prop('story_dir')."/meta.txt" or die $!;
529              
530 0           my $task_name = get_prop('task_name');
531              
532             #note( ( nocolor() ? short_story_name($task_name) : colored( ['yellow'], short_story_name($task_name) ) ));
533              
534 0           while (my $i = ){
535 0           chomp $i;
536 0           $i='@ '.$i;
537 0 0         note( nocolor() ? $i : colored( ['magenta'], "$i" ));
538             }
539 0           close META;
540              
541             }
542              
543             sub short_story_name {
544              
545 0     0     my $task_name = shift;
546              
547 0           my $story_dir = get_prop('story_dir');
548              
549 0           my $cwd_size = scalar(split /\//, get_prop('project_root_dir'));
550              
551 0           my $short_story_dir;
552              
553             my $i;
554              
555 0           for my $l (split /\//, $story_dir){
556 0 0         $short_story_dir.=$l. ( ($^O =~ 'MSWin') ? "\\" : "/" ) unless $i++ < $cwd_size;
    0          
557             }
558              
559 0           my $story_vars = story_vars_pretty();
560              
561              
562 0 0 0       $short_story_dir ||= ($^O =~ 'MSWin') ? "\\" : "/";
563              
564 0           my @ret;
565              
566 0 0         push @ret, "$short_story_dir" if $short_story_dir;
567 0 0         push @ret, "{$story_vars}" if $story_vars;
568              
569 0           join "", @ret;
570              
571             }
572              
573             sub timestamp {
574              
575 0     0     sprintf '%02d-%02d-%02d %02d:%02d:%02d',
576             localtime->year()+1900,
577             localtime->mon()+1, localtime->mday,
578             localtime->hour, localtime->min, localtime->sec;
579              
580             }
581              
582             END {
583              
584             #print "STATUS: $STATUS\n";
585              
586             if ($STATUS == 1){
587             exit(0);
588             } elsif($STATUS == -1){
589             exit(1);
590             } else{
591             exit(2);
592             }
593              
594            
595             }
596              
597             1;
598              
599              
600             __END__