| 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__ |