Utils.pm 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # ABSTRACT: Utilities for the testrail command line functions, and their main loops.
  2. # PODNAME: TestRail::Utils
  3. package TestRail::Utils;
  4. use strict;
  5. use warnings;
  6. use Carp qw{confess cluck};
  7. use Pod::Usage ();
  8. use TestRail::API;
  9. use IO::Interactive::Tiny ();
  10. use Term::ANSIColor 2.01 qw(colorstrip);
  11. use Term::ReadKey ();
  12. use Scalar::Util qw{blessed};
  13. =head1 SCRIPT HELPER FUNCTIONS
  14. =head2 help
  15. Print the perldoc for $0 and exit.
  16. =cut
  17. sub help {
  18. Pod::Usage::pod2usage( '-verbose' => 2, '-noperldoc' => 1, '-exitval' => 'NOEXIT' );
  19. return 0;
  20. }
  21. =head2 userInput
  22. Wait for user input and return it.
  23. =cut
  24. sub userInput {
  25. my ( $echo_ok ) = @_;
  26. my $input = "";
  27. # I'm going to be super paranoid here and consider everything to be sensitive info by default.
  28. Term::ReadKey::ReadMode('noecho') unless $echo_ok;
  29. {
  30. local $SIG{'INT'} = sub { Term::ReadKey::ReadMode(0); exit 130; };
  31. $input = <STDIN>;
  32. chomp($input) if $input;
  33. }
  34. Term::ReadKey::ReadMode(0) unless $echo_ok;
  35. print "\n";
  36. return $input;
  37. }
  38. =head2 interrogateUser($options,@keys)
  39. Wait for specified keys via userInput, and put them into $options HASHREF, if they are not already defined.
  40. Returns modified $options HASHREF.
  41. Dies if the user provides no value.
  42. =cut
  43. sub interrogateUser {
  44. my ($options,@keys) = @_;
  45. foreach my $key (@keys) {
  46. if (!$options->{$key}) {
  47. print "Type the $key for your TestRail install below:\n";
  48. $options->{$key} = TestRail::Utils::userInput( $key ne 'password' );
  49. die "$key cannot be blank!" unless $options->{$key};
  50. }
  51. }
  52. return $options;
  53. }
  54. =head2 parseConfig(homedir)
  55. Parse .testrailrc in the provided home directory.
  56. Returns:
  57. ARRAY - (apiurl,password,user)
  58. =cut
  59. sub parseConfig {
  60. my ($homedir,$login_only) = @_;
  61. my $results = {};
  62. my $arr =[];
  63. open(my $fh, '<', $homedir . '/.testrailrc') or return (undef,undef,undef);#couldn't open!
  64. while (<$fh>) {
  65. chomp;
  66. @$arr = split(/=/,$_);
  67. if (scalar(@$arr) != 2) {
  68. warn("Could not parse $_ in '$homedir/.testrailrc'!\n");
  69. next;
  70. }
  71. $results->{lc($arr->[0])} = $arr->[1];
  72. }
  73. close($fh);
  74. return ($results->{'apiurl'},$results->{'password'},$results->{'user'}) if $login_only;
  75. return $results;
  76. }
  77. =head2 getFilenameFromTapLine($line)
  78. Analyze TAP output by prove and look for filename boundaries (no other way to figure out what file is run).
  79. Long story short: don't end 'unknown' TAP lines with any number of dots if you don't want it interpreted as a test name.
  80. Apparently this is the TAP way of specifying the file that's run...which is highly inadequate.
  81. Inputs:
  82. STRING LINE - some line of TAP
  83. Returns:
  84. STRING filename of the test that output the TAP.
  85. =cut
  86. sub getFilenameFromTapLine {
  87. my $orig = shift;
  88. $orig =~ s/ *$//g; # Strip all trailing whitespace
  89. #Special case
  90. my ($is_skipall) = $orig =~ /(.*)\.+ skipped:/;
  91. return $is_skipall if $is_skipall;
  92. my @process_split = split(/ /,$orig);
  93. return 0 unless scalar(@process_split);
  94. my $dotty = pop @process_split; #remove the ........ (may repeat a number of times)
  95. return 0 if $dotty =~ /\d/; #Apparently looking for literal dots returns numbers too. who knew?
  96. chomp $dotty;
  97. my $line = join(' ',@process_split);
  98. #IF it ends in a bunch of dots
  99. #AND it isn't an ok/not ok
  100. #AND it isn't a comment
  101. #AND it isn't blank
  102. #THEN it's a test name
  103. return $line if ($dotty =~ /^\.+$/ && !($line =~ /^ok|not ok/) && !($line =~ /^# /) && $line);
  104. return 0;
  105. }
  106. =head2 TAP2TestFiles(file)
  107. Returns ARRAY of TAP output for the various test files therein.
  108. file is optional, will read TAP from STDIN if not passed.
  109. =cut
  110. sub TAP2TestFiles {
  111. my $file = shift;
  112. my ($fh,$fcontents,@files);
  113. if ($file) {
  114. open($fh,'<',$file);
  115. while (<$fh>) {
  116. $_ = colorstrip($_); #strip prove brain damage
  117. if (getFilenameFromTapLine($_)) {
  118. push(@files,$fcontents) if $fcontents;
  119. $fcontents = '';
  120. }
  121. $fcontents .= $_;
  122. }
  123. close($fh);
  124. push(@files,$fcontents) if $fcontents;
  125. } else {
  126. #Just read STDIN, print help if no file was passed
  127. die "ERROR: no file passed, and no data piped in! See --help for usage.\n" if IO::Interactive::Tiny::is_interactive();
  128. while (<>) {
  129. $_ = colorstrip($_); #strip prove brain damage
  130. if (getFilenameFromTapLine($_)) {
  131. push(@files,$fcontents) if $fcontents;
  132. $fcontents = '';
  133. }
  134. $fcontents .= $_;
  135. }
  136. push(@files,$fcontents) if $fcontents;
  137. }
  138. return @files;
  139. }
  140. =head2 getRunInformation
  141. Return the relevant project definition, plan, run and milestone definition HASHREFs for the provided options.
  142. Dies in the event the project/plan/run could not be found.
  143. =cut
  144. sub getRunInformation {
  145. my ($tr,$opts) = @_;
  146. confess("First argument must be instance of TestRail::API") unless blessed($tr) eq 'TestRail::API';
  147. my $project = $tr->getProjectByName($opts->{'project'});
  148. confess "No such project '$opts->{project}'.\n" if !$project;
  149. my ($run,$plan);
  150. if ($opts->{'plan'}) {
  151. $plan = $tr->getPlanByName($project->{'id'},$opts->{'plan'});
  152. confess "No such plan '$opts->{plan}'!\n" if !$plan;
  153. $run = $tr->getChildRunByName($plan,$opts->{'run'}, $opts->{'configs'});
  154. } else {
  155. $run = $tr->getRunByName($project->{'id'},$opts->{'run'});
  156. }
  157. confess "No such run '$opts->{run}' matching the provided configs (if any).\n" if !$run;
  158. #If the run/plan has a milestone set, then return it too
  159. my $milestone;
  160. my $mid = $plan ? $plan->{'milestone_id'} : $run->{'milestone_id'};
  161. if ($mid) {
  162. $milestone = $tr->getMilestoneByID($mid);
  163. confess "Could not fetch run milestone!" unless $milestone; #hope this doesn't happen
  164. }
  165. return ($project, $plan, $run, $milestone);
  166. }
  167. =head2 getHandle(opts)
  168. Convenience method for binaries and testing.
  169. Returns a new TestRail::API when passed an options hash such as is built by most of the binaries,
  170. or returned by parseConfig.
  171. Has a special 'mock' hash key that can only be used by those testing this distribution during 'make test'.
  172. =cut
  173. sub getHandle {
  174. my $opts = shift;
  175. $opts->{'debug'} = 1 if ($opts->{'browser'});
  176. my $tr = TestRail::API->new($opts->{apiurl},$opts->{user},$opts->{password},$opts->{'encoding'},$opts->{'debug'});
  177. if ($opts->{'browser'}) {
  178. $tr->{'browser'} = $opts->{'browser'};
  179. $tr->{'debug'} = 0;
  180. }
  181. return $tr;
  182. }
  183. 1;
  184. __END__
  185. =head1 SPECIAL THANKS
  186. Thanks to cPanel Inc, for graciously funding the creation of this module.