| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # ABSTRACT: Utilities for the testrail command line functions, and their main loops. | 
| 2 |  |  |  |  |  |  | # PODNAME: TestRail::Utils | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | package TestRail::Utils; | 
| 5 |  |  |  |  |  |  | $TestRail::Utils::VERSION = '0.052'; | 
| 6 | 14 |  |  | 14 |  | 196759 | use strict; | 
|  | 14 |  |  |  |  | 49 |  | 
|  | 14 |  |  |  |  | 398 |  | 
| 7 | 14 |  |  | 14 |  | 68 | use warnings; | 
|  | 14 |  |  |  |  | 29 |  | 
|  | 14 |  |  |  |  | 403 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 14 |  |  | 14 |  | 68 | use Carp       qw{confess cluck}; | 
|  | 14 |  |  |  |  | 25 |  | 
|  | 14 |  |  |  |  | 780 |  | 
| 10 | 14 |  |  | 14 |  | 6760 | use Pod::Usage (); | 
|  | 14 |  |  |  |  | 530889 |  | 
|  | 14 |  |  |  |  | 430 |  | 
| 11 | 14 |  |  | 14 |  | 4061 | use TestRail::API; | 
|  | 14 |  |  |  |  | 41 |  | 
|  | 14 |  |  |  |  | 566 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 14 |  |  | 14 |  | 8078 | use IO::Interactive::Tiny (); | 
|  | 14 |  |  |  |  | 184 |  | 
|  | 14 |  |  |  |  | 440 |  | 
| 14 | 14 |  |  | 14 |  | 7362 | use Term::ANSIColor 2.01  qw(colorstrip); | 
|  | 14 |  |  |  |  | 103122 |  | 
|  | 14 |  |  |  |  | 12224 |  | 
| 15 | 14 |  |  | 14 |  | 6814 | use Term::ReadKey         (); | 
|  | 14 |  |  |  |  | 26152 |  | 
|  | 14 |  |  |  |  | 419 |  | 
| 16 | 14 |  |  | 14 |  | 107 | use Scalar::Util          qw{blessed}; | 
|  | 14 |  |  |  |  | 30 |  | 
|  | 14 |  |  |  |  | 15943 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | sub help { | 
| 19 | 9 |  |  | 9 | 1 | 1593 | Pod::Usage::pod2usage( | 
| 20 |  |  |  |  |  |  | '-verbose'   => 2, | 
| 21 |  |  |  |  |  |  | '-noperldoc' => 1, | 
| 22 |  |  |  |  |  |  | '-exitval'   => 'NOEXIT' | 
| 23 |  |  |  |  |  |  | ); | 
| 24 | 9 |  |  |  |  | 203409 | return 0; | 
| 25 |  |  |  |  |  |  | } | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | sub userInput { | 
| 28 | 0 |  |  | 0 | 1 | 0 | my ($echo_ok) = @_; | 
| 29 | 0 |  |  |  |  | 0 | my $input = ""; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | # I'm going to be super paranoid here and consider everything to be sensitive info by default. | 
| 32 | 0 | 0 |  |  |  | 0 | Term::ReadKey::ReadMode('noecho') unless $echo_ok; | 
| 33 |  |  |  |  |  |  | { | 
| 34 | 0 |  |  | 0 |  | 0 | local $SIG{'INT'} = sub { Term::ReadKey::ReadMode(0); exit 130; }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 35 | 0 |  |  |  |  | 0 | $input = ; | 
| 36 | 0 | 0 |  |  |  | 0 | chomp($input) if $input; | 
| 37 |  |  |  |  |  |  | } | 
| 38 | 0 | 0 |  |  |  | 0 | Term::ReadKey::ReadMode(0) unless $echo_ok; | 
| 39 | 0 |  |  |  |  | 0 | print "\n"; | 
| 40 | 0 |  |  |  |  | 0 | return $input; | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | sub interrogateUser { | 
| 44 | 43 |  |  | 43 | 1 | 381 | my ( $options, @keys ) = @_; | 
| 45 | 43 |  |  |  |  | 179 | foreach my $key (@keys) { | 
| 46 | 188 | 50 |  |  |  | 450 | if ( !$options->{$key} ) { | 
| 47 | 0 |  |  |  |  | 0 | print "Type the $key for your TestRail install below:\n"; | 
| 48 | 0 |  |  |  |  | 0 | $options->{$key} = TestRail::Utils::userInput( $key ne 'password' ); | 
| 49 | 0 | 0 |  |  |  | 0 | die "$key cannot be blank!" unless $options->{$key}; | 
| 50 |  |  |  |  |  |  | } | 
| 51 |  |  |  |  |  |  | } | 
| 52 | 43 |  |  |  |  | 111 | return $options; | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub parseConfig { | 
| 56 | 2 |  |  | 2 | 1 | 7683 | my ( $homedir, $login_only ) = @_; | 
| 57 | 2 |  |  |  |  | 6 | my $results = {}; | 
| 58 | 2 |  |  |  |  | 4 | my $arr     = []; | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 2 | 50 |  |  |  | 67 | open( my $fh, '<', $homedir . '/.testrailrc' ) | 
| 61 |  |  |  |  |  |  | or return ( undef, undef, undef );    #couldn't open! | 
| 62 | 2 |  |  |  |  | 39 | while (<$fh>) { | 
| 63 | 34 |  |  |  |  | 43 | chomp; | 
| 64 | 34 |  |  |  |  | 83 | @$arr = split( /=/, $_ ); | 
| 65 | 34 | 100 |  |  |  | 60 | if ( scalar(@$arr) != 2 ) { | 
| 66 | 4 |  |  |  |  | 87 | warn("Could not parse $_ in '$homedir/.testrailrc'!\n"); | 
| 67 | 4 |  |  |  |  | 21 | next; | 
| 68 |  |  |  |  |  |  | } | 
| 69 | 30 |  |  |  |  | 91 | $results->{ lc( $arr->[0] ) } = $arr->[1]; | 
| 70 |  |  |  |  |  |  | } | 
| 71 | 2 |  |  |  |  | 20 | close($fh); | 
| 72 | 2 | 100 |  |  |  | 19 | return ( $results->{'apiurl'}, $results->{'password'}, $results->{'user'} ) | 
| 73 |  |  |  |  |  |  | if $login_only; | 
| 74 | 1 |  |  |  |  | 8 | return $results; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub getFilenameFromTapLine { | 
| 78 | 392 |  |  | 392 | 1 | 4737 | my $orig = shift; | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 392 |  |  |  |  | 2400 | $orig =~ s/ *$//g;    # Strip all trailing whitespace | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | #Special case | 
| 83 | 392 |  |  |  |  | 707 | my ($is_skipall) = $orig =~ /(.*)\.+ skipped:/; | 
| 84 | 392 | 100 |  |  |  | 615 | return $is_skipall if $is_skipall; | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 390 |  |  |  |  | 920 | my @process_split = split( / /, $orig ); | 
| 87 | 390 | 100 |  |  |  | 697 | return 0 unless scalar(@process_split); | 
| 88 | 363 |  |  |  |  | 464 | my $dotty = | 
| 89 |  |  |  |  |  |  | pop @process_split;    #remove the ........ (may repeat a number of times) | 
| 90 | 363 | 100 |  |  |  | 804 | return 0 | 
| 91 |  |  |  |  |  |  | if $dotty =~ | 
| 92 |  |  |  |  |  |  | /\d/;  #Apparently looking for literal dots returns numbers too. who knew? | 
| 93 | 244 |  |  |  |  | 287 | chomp $dotty; | 
| 94 | 244 |  |  |  |  | 460 | my $line = join( ' ', @process_split ); | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | #IF it ends in a bunch of dots | 
| 97 |  |  |  |  |  |  | #AND it isn't an ok/not ok | 
| 98 |  |  |  |  |  |  | #AND it isn't a comment | 
| 99 |  |  |  |  |  |  | #AND it isn't blank | 
| 100 |  |  |  |  |  |  | #THEN it's a test name | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 244 | 50 | 66 |  |  | 835 | return $line | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 103 |  |  |  |  |  |  | if ( $dotty =~ /^\.+$/ | 
| 104 |  |  |  |  |  |  | && !( $line =~ /^ok|not ok/ ) | 
| 105 |  |  |  |  |  |  | && !( $line =~ /^# / ) | 
| 106 |  |  |  |  |  |  | && $line ); | 
| 107 | 204 |  |  |  |  | 401 | return 0; | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | sub TAP2TestFiles { | 
| 111 | 9 |  |  | 9 | 1 | 1524 | my $file = shift; | 
| 112 | 9 |  |  |  |  | 20 | my ( $fh, $fcontents, @files ); | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 9 | 50 |  |  |  | 22 | if ($file) { | 
| 115 | 9 |  |  |  |  | 331 | open( $fh, '<', $file ); | 
| 116 | 9 |  |  |  |  | 190 | while (<$fh>) { | 
| 117 | 197 |  |  |  |  | 363 | $_ = colorstrip($_);    #strip prove brain damage | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 197 | 100 |  |  |  | 1433 | if ( getFilenameFromTapLine($_) ) { | 
| 120 | 17 | 100 |  |  |  | 57 | push( @files, $fcontents ) if $fcontents; | 
| 121 | 17 |  |  |  |  | 29 | $fcontents = ''; | 
| 122 |  |  |  |  |  |  | } | 
| 123 | 197 |  |  |  |  | 618 | $fcontents .= $_; | 
| 124 |  |  |  |  |  |  | } | 
| 125 | 9 |  |  |  |  | 101 | close($fh); | 
| 126 | 9 | 50 |  |  |  | 51 | push( @files, $fcontents ) if $fcontents; | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  | else { | 
| 129 |  |  |  |  |  |  | #Just read STDIN, print help if no file was passed | 
| 130 | 0 | 0 |  |  |  | 0 | die | 
| 131 |  |  |  |  |  |  | "ERROR: no file passed, and no data piped in! See --help for usage.\n" | 
| 132 |  |  |  |  |  |  | if IO::Interactive::Tiny::is_interactive(); | 
| 133 | 0 |  |  |  |  | 0 | while (<>) { | 
| 134 | 0 |  |  |  |  | 0 | $_ = colorstrip($_);    #strip prove brain damage | 
| 135 | 0 | 0 |  |  |  | 0 | if ( getFilenameFromTapLine($_) ) { | 
| 136 | 0 | 0 |  |  |  | 0 | push( @files, $fcontents ) if $fcontents; | 
| 137 | 0 |  |  |  |  | 0 | $fcontents = ''; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 0 |  |  |  |  | 0 | $fcontents .= $_; | 
| 140 |  |  |  |  |  |  | } | 
| 141 | 0 | 0 |  |  |  | 0 | push( @files, $fcontents ) if $fcontents; | 
| 142 |  |  |  |  |  |  | } | 
| 143 | 9 |  |  |  |  | 55 | return @files; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | sub getRunInformation { | 
| 147 | 46 |  |  | 46 | 1 | 7670 | my ( $tr, $opts ) = @_; | 
| 148 | 46 | 50 |  |  |  | 150 | confess("First argument must be instance of TestRail::API") | 
| 149 |  |  |  |  |  |  | unless blessed($tr) eq 'TestRail::API'; | 
| 150 |  |  |  |  |  |  |  | 
| 151 | 46 |  |  |  |  | 154 | my $project = $tr->getProjectByName( $opts->{'project'} ); | 
| 152 | 46 | 100 |  |  |  | 261 | confess "No such project '$opts->{project}'.\n" if !$project; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 45 |  |  |  |  | 67 | my ( $run, $plan ); | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 45 | 100 |  |  |  | 95 | if ( $opts->{'plan'} ) { | 
| 157 | 24 |  |  |  |  | 74 | $plan = $tr->getPlanByName( $project->{'id'}, $opts->{'plan'} ); | 
| 158 | 24 | 100 |  |  |  | 1624 | confess "No such plan '$opts->{plan}'!\n" if !$plan; | 
| 159 |  |  |  |  |  |  | $run = | 
| 160 | 23 |  |  |  |  | 105 | $tr->getChildRunByName( $plan, $opts->{'run'}, $opts->{'configs'} ); | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  | else { | 
| 163 | 21 |  |  |  |  | 103 | $run = $tr->getRunByName( $project->{'id'}, $opts->{'run'} ); | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 44 | 100 |  |  |  | 959 | confess | 
| 167 |  |  |  |  |  |  | "No such run '$opts->{run}' matching the provided configs (if any).\n" | 
| 168 |  |  |  |  |  |  | if !$run; | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | #If the run/plan has a milestone set, then return it too | 
| 171 | 41 |  |  |  |  | 59 | my $milestone; | 
| 172 | 41 | 100 |  |  |  | 92 | my $mid = $plan ? $plan->{'milestone_id'} : $run->{'milestone_id'}; | 
| 173 | 41 | 100 |  |  |  | 107 | if ($mid) { | 
| 174 | 20 |  |  |  |  | 55 | $milestone = $tr->getMilestoneByID($mid); | 
| 175 | 20 | 50 |  |  |  | 690 | confess "Could not fetch run milestone!" | 
| 176 |  |  |  |  |  |  | unless $milestone;    #hope this doesn't happen | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 41 |  |  |  |  | 150 | return ( $project, $plan, $run, $milestone ); | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub getHandle { | 
| 183 | 37 |  |  | 37 | 1 | 732 | my $opts = shift; | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 37 | 50 |  |  |  | 331 | $opts->{'debug'} = 1 if ( $opts->{'browser'} ); | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | my $tr = TestRail::API->new( | 
| 188 |  |  |  |  |  |  | $opts->{apiurl},     $opts->{user}, $opts->{password}, | 
| 189 | 37 |  |  |  |  | 576 | $opts->{'encoding'}, $opts->{'debug'} | 
| 190 |  |  |  |  |  |  | ); | 
| 191 | 37 | 50 |  |  |  | 151 | if ( $opts->{'browser'} ) { | 
| 192 | 37 |  |  |  |  | 907 | $tr->{'browser'} = $opts->{'browser'}; | 
| 193 | 37 |  |  |  |  | 97 | $tr->{'debug'}   = 0; | 
| 194 |  |  |  |  |  |  | } | 
| 195 | 37 |  |  |  |  | 138 | return $tr; | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | 1; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | __END__ |