File Coverage

blib/lib/Outthentic.pm
Criterion Covered Total %
statement 39 290 13.4
branch 0 162 0.0
condition 0 24 0.0
subroutine 13 30 43.3
pod n/a
total 52 506 10.2


line stmt bran cond sub pod time code
1             package Outthentic;
2              
3             our $VERSION = '0.3.16';
4              
5             1;
6              
7             package main;
8              
9 1     1   643 use Carp;
  1         2  
  1         46  
10 1     1   578 use Config::General;
  1         18764  
  1         44  
11 1     1   376 use YAML qw{LoadFile};
  1         5499  
  1         38  
12 1     1   509 use JSON;
  1         6359  
  1         4  
13 1     1   85 use Cwd;
  1         2  
  1         40  
14              
15 1     1   5 use strict;
  1         1  
  1         14  
16 1     1   462 use Data::Dumper;
  1         5138  
  1         52  
17 1     1   612 use File::Temp qw/ tempfile /;
  1         7492  
  1         45  
18 1     1   376 use Outthentic::Story;
  1         4  
  1         171  
19 1     1   724 use Term::ANSIColor;
  1         10838  
  1         73  
20 1     1   602 use Hash::Merge qw{merge};
  1         14139  
  1         71  
21 1     1   634 use Time::localtime;
  1         6256  
  1         70  
22 1     1   652 use Capture::Tiny;
  1         8144  
  1         4923  
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              
258 0 0         if ($format eq 'production') {
    0          
259 0   0       note(
260             timestamp().' : '.($task_name || '').' '.(short_story_name($task_name))
261             );
262             } elsif ($format ne 'concise') {
263 0 0 0       note(
264             timestamp().' : '.($task_name || '' ).' '.(nocolor() ? short_story_name($task_name) : colored(['yellow'],short_story_name($task_name)))
265             );
266             }
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         if ($ex_code == 0) {
    0          
356 0 0         outh_ok(1, "scenario succeeded" ) unless $format eq 'production';
357 0           set_prop( scenario_status => 1 );
358 0           Outthentic::Story::Stat->set_scenario_status(1);
359 0           Outthentic::Story::Stat->set_stdout($out);
360              
361             }elsif(ignore_story_err()){
362 0           outh_ok(1, "scenario failed, still continue due to `ignore_story_err' is set");
363 0           set_prop( scenario_status => 2 );
364 0           Outthentic::Story::Stat->set_scenario_status(2);
365 0           Outthentic::Story::Stat->set_stdout($out);
366             }else{
367 0 0         if ( $format eq 'production'){
368 0           print "$out";
369 0           outh_ok(0, "scenario succeeded", $ex_code);
370             } else {
371 0           outh_ok(0, "scenario succeeded", $ex_code);
372             }
373 0           set_prop( scenario_status => 0 );
374 0           Outthentic::Story::Stat->set_scenario_status(0);
375 0           Outthentic::Story::Stat->set_stdout($out);
376 0           Outthentic::Story::Stat->set_status(0);
377             }
378              
379 0           set_prop( stdout => $out );
380              
381             }
382              
383              
384 0           return get_prop('stdout');
385             }
386              
387             sub header {
388              
389 0     0     my $project = project_root_dir();
390 0           my $story = get_prop('story');
391 0           my $story_type = get_prop('story_type');
392 0           my $story_file = get_prop('story_file');
393 0           my $debug = get_prop('debug');
394 0           my $ignore_story_err = ignore_story_err();
395            
396 0           note("project: $project");
397 0           note("story: $story");
398 0           note("story_type: $story_type");
399 0           note("debug: $debug");
400 0           note("ignore story errors: $ignore_story_err");
401              
402             }
403              
404             sub run_and_check {
405              
406 0     0     my $story_check_file = shift;
407              
408 0           my $format = get_prop('format');
409              
410 0 0         header() if debug_mod2();
411              
412 0           dsl()->{debug_mod} = get_prop('debug');
413              
414 0           dsl()->{match_l} = get_prop('match_l');
415              
416 0           eval { dsl()->{output} = run_story_file() };
  0            
417              
418            
419 0 0         if ($@) {
420 0           $STATUS = 0;
421 0           die "story run error: $@";
422             }
423              
424 0 0         return unless get_prop('scenario_status'); # we don't run checks for failed scenarios
425              
426 0 0         return unless $story_check_file;
427 0 0         return unless -s $story_check_file; # don't run check when check file is empty
428              
429 0           eval {
430 0 0         open my $fh, $story_check_file or confess $!;
431 0           my $check_list = join "", <$fh>; close $fh;
  0            
432 0           dsl()->validate($check_list)
433             };
434              
435 0           my $err = $@;
436 0           my $check_fail=0;
437 0           for my $r ( @{dsl()->results}){
  0            
438 0 0         note($r->{message}) if $r->{type} eq 'debug';
439 0 0         if ($r->{type} eq 'check_expression' ){
440 0           Outthentic::Story::Stat->add_check_stat($r);
441 0 0         $check_fail=1 unless $r->{status};
442 0 0         if ($format eq 'production'){
443 0 0         outh_ok($r->{status}, $r->{message}) unless $r->{status};
444             } else {
445 0           outh_ok($r->{status}, $r->{message});
446             }
447 0 0         Outthentic::Story::Stat->set_status(0) unless $r->{status};
448             };
449              
450             }
451              
452              
453 0 0         if ($err) {
454 0           $STATUS = 0;
455 0           die "validator error: $err";
456             }
457              
458 0 0 0       if ($format eq 'production' and $check_fail) {
459 0           print get_prop("stdout");
460             }
461             }
462              
463            
464              
465             sub outh_ok {
466              
467 0     0     my $status = shift;
468 0           my $message = shift;
469 0           my $exit_code = shift;
470              
471 0           my $format = get_prop('format');
472              
473 0 0         if ($format ne 'concise'){
474 0 0         if ($status) {
475 0 0         print nocolor() ? "ok\t$message\n" : colored(['green'],"ok\t$message")."\n";
476             } else {
477 0 0         print nocolor() ? "not ok\t$message\n" : colored(['red'], "not ok\t$message")."\n";
478             }
479             }
480              
481 0 0 0       if ($status == 0 and $STATUS != 0 ){
482 0 0         $STATUS = ($exit_code == 1 ) ? -1 : 0;
483             }
484             }
485              
486             sub note {
487              
488 0     0     my $message = shift;
489              
490 0           binmode(STDOUT, ":utf8");
491 0           print "$message\n";
492              
493             }
494              
495              
496             sub print_meta {
497              
498 0 0   0     open META, get_prop('story_dir')."/meta.txt" or die $!;
499              
500 0           my $task_name = get_prop('task_name');
501              
502             #note( ( nocolor() ? short_story_name($task_name) : colored( ['yellow'], short_story_name($task_name) ) ));
503              
504 0           while (my $i = ){
505 0           chomp $i;
506 0           $i='@ '.$i;
507 0 0         note( nocolor() ? $i : colored( ['magenta'], "$i" ));
508             }
509 0           close META;
510              
511             }
512              
513             sub short_story_name {
514              
515 0     0     my $task_name = shift;
516              
517 0           my $story_dir = get_prop('story_dir');
518              
519 0           my $cwd_size = scalar(split /\//, get_prop('project_root_dir'));
520              
521 0           my $short_story_dir;
522              
523             my $i;
524              
525 0           for my $l (split /\//, $story_dir){
526 0 0         $short_story_dir.=$l."/" unless $i++ < $cwd_size;
527              
528             }
529              
530 0           my $story_vars = story_vars_pretty();
531              
532 0   0       $short_story_dir ||= "/";
533              
534 0           my @ret;
535              
536 0 0         push @ret, "[path] $short_story_dir" if $short_story_dir;
537 0 0         push @ret, "[params] $story_vars" if $story_vars;
538              
539 0           join " ", @ret;
540              
541             }
542              
543             sub timestamp {
544 0     0     sprintf '%02d-%02d-%02d %02d:%02d:%02d',
545             localtime->year()+1900,
546             localtime->mon()+1, localtime->mday,
547             localtime->hour, localtime->min, localtime->sec;
548             }
549              
550             END {
551              
552             #print "STATUS: $STATUS\n";
553              
554             if ($STATUS == 1){
555             exit(0);
556             } elsif($STATUS == -1){
557             exit(1);
558             } else{
559             exit(2);
560             }
561              
562            
563             }
564              
565             1;
566              
567              
568             __END__