Utils.pm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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::Perldoc 3.10;
  8. use Scalar::Util qw{blessed};
  9. use File::Find;
  10. use Cwd qw{abs_path};
  11. use File::Basename qw{basename};
  12. =head1 SCRIPT HELPER FUNCTIONS
  13. =head2 help
  14. Print the perldoc for $0 and exit.
  15. =cut
  16. sub help {
  17. @ARGV = ($0);
  18. Pod::Perldoc->run();
  19. exit 0;
  20. }
  21. =head2 userInput
  22. Wait for user input and return it.
  23. =cut
  24. sub userInput {
  25. local $| = 1;
  26. my $rt = <STDIN>;
  27. chomp $rt;
  28. return $rt;
  29. }
  30. =head2 interrogateUser($options,@keys)
  31. Wait for specified keys via userInput, and put them into $options HASHREF, if they are not already defined.
  32. Returns modified $options HASHREF.
  33. Dies if the user provides no value.
  34. =cut
  35. sub interrogateUser {
  36. my ($options,@keys) = @_;
  37. foreach my $key (@keys) {
  38. if (!$options->{$key}) {
  39. print "Type the $key for your testLink install below:\n";
  40. $options->{$key} = TestRail::Utils::userInput();
  41. die "$key cannot be blank!" unless $options->{$key};
  42. }
  43. }
  44. return $options;
  45. }
  46. =head2 parseConfig(homedir)
  47. Parse .testrailrc in the provided home directory.
  48. Returns:
  49. ARRAY - (apiurl,password,user)
  50. =cut
  51. sub parseConfig {
  52. my ($homedir,$login_only) = @_;
  53. my $results = {};
  54. my $arr =[];
  55. open(my $fh, '<', $homedir . '/.testrailrc') or return (undef,undef,undef);#couldn't open!
  56. while (<$fh>) {
  57. chomp;
  58. @$arr = split(/=/,$_);
  59. if (scalar(@$arr) != 2) {
  60. warn("Could not parse $_ in '$homedir/.testrailrc'!\n");
  61. next;
  62. }
  63. $results->{lc($arr->[0])} = $arr->[1];
  64. }
  65. close($fh);
  66. return ($results->{'apiurl'},$results->{'password'},$results->{'user'}) if $login_only;
  67. return $results;
  68. }
  69. =head2 getFilenameFromTAPLine($line)
  70. Analyze TAP output by prove and look for filename boundaries (no other way to figure out what file is run).
  71. 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.
  72. Apparently this is the TAP way of specifying the file that's run...which is highly inadequate.
  73. Inputs:
  74. STRING LINE - some line of TAP
  75. Returns:
  76. STRING filename of the test that output the TAP.
  77. =cut
  78. sub getFilenameFromTapLine {
  79. my $orig = shift;
  80. $orig =~ s/ *$//g; # Strip all trailing whitespace
  81. #Special case
  82. my ($is_skipall) = $orig =~ /(.*)\.+ skipped:/;
  83. return $is_skipall if $is_skipall;
  84. my @process_split = split(/ /,$orig);
  85. return 0 unless scalar(@process_split);
  86. my $dotty = pop @process_split; #remove the ........ (may repeat a number of times)
  87. return 0 if $dotty =~ /\d/; #Apparently looking for literal dots returns numbers too. who knew?
  88. chomp $dotty;
  89. my $line = join(' ',@process_split);
  90. #IF it ends in a bunch of dots
  91. #AND it isn't an ok/not ok
  92. #AND it isn't a comment
  93. #AND it isn't blank
  94. #THEN it's a test name
  95. return $line if ($dotty =~ /^\.+$/ && !($line =~ /^ok|not ok/) && !($line =~ /^# /) && $line);
  96. return 0;
  97. }
  98. =head2 getRunInformation
  99. Return the relevant project definition, plan, run and milestone definition HASHREFs for the provided options.
  100. Dies in the event the project/plan/run could not be found.
  101. =cut
  102. sub getRunInformation {
  103. my ($tr,$opts) = @_;
  104. confess("First argument must be instance of TestRail::API") unless blessed($tr) eq 'TestRail::API';
  105. my $project = $tr->getProjectByName($opts->{'project'});
  106. confess "No such project '$opts->{project}'.\n" if !$project;
  107. my ($run,$plan);
  108. if ($opts->{'plan'}) {
  109. $plan = $tr->getPlanByName($project->{'id'},$opts->{'plan'});
  110. confess "No such plan '$opts->{plan}'!\n" if !$plan;
  111. $run = $tr->getChildRunByName($plan,$opts->{'run'}, $opts->{'configs'});
  112. } else {
  113. $run = $tr->getRunByName($project->{'id'},$opts->{'run'});
  114. }
  115. confess "No such run '$opts->{run}' matching the provided configs (if any).\n" if !$run;
  116. #If the run/plan has a milestone set, then return it too
  117. my $milestone;
  118. my $mid = $plan ? $plan->{'milestone_id'} : $run->{'milestone_id'};
  119. if ($mid) {
  120. $milestone = $tr->getMilestoneByID($mid);
  121. confess "Could not fetch run milestone!" unless $milestone; #hope this doesn't happen
  122. }
  123. return ($project, $plan, $run, $milestone);
  124. }
  125. =head2 findTests(opts,case1,...,caseN)
  126. Given an ARRAY of tests, find tests meeting your criteria (or not) in the specified directory.
  127. =over 4
  128. =item HASHREF C<OPTS> - Options for finding tests:
  129. =over 4
  130. =item STRING C<MATCH> - Only return tests which exist in the path provided. Mutually exclusive with no-match.
  131. =item STRING C<NO-MATCH> - Only return tests which aren't in the path provided (orphan tests). Mutually exclusive with match.
  132. =item BOOL C<NO-RECURSE> - Do not do a recursive scan for files.
  133. =item BOOL C<NAMES-ONLY> - Only return the names of the tests rather than the entire test objects.
  134. =back
  135. =item ARRAY C<CASES> - Array of cases to translate to pathnames based on above options.
  136. =back
  137. Returns tests found that meet the criteria laid out in the options.
  138. Provides absolute path to tests if match is passed; this is the 'full_title' key if names-only is false/undef.
  139. Dies if mutually exclusive options are passed.
  140. =cut
  141. sub findTests {
  142. my ($opts,@cases) = @_;
  143. confess "Error! match and no-match options are mutually exclusive.\n" if ($opts->{'match'} && $opts->{'no-match'});
  144. my @tests = @cases;
  145. my (@realtests);
  146. if ($opts->{'match'} || $opts->{'no-match'}) {
  147. my $dir = $opts->{'match'} ? $opts->{'match'} : $opts->{'no-match'};
  148. if (!$opts->{'no-recurse'}) {
  149. File::Find::find( sub { push(@realtests,$File::Find::name) if -f }, $dir );
  150. @tests = grep {my $real = $_->{'title'}; grep { $real eq basename($_) } @realtests} @cases; #XXX if you have dups in your tree, be-ware
  151. } else {
  152. #Handle special windows case -- glob doesn't prepend abspath
  153. @realtests = glob("$dir/*");
  154. @tests = map {
  155. $_->{'title'} = "$dir/".$_->{'title'} if( $^O eq 'MSWin32' );
  156. $_
  157. } grep {my $fname = $_->{'title'}; grep { basename($_) eq $fname} @realtests } @cases;
  158. }
  159. @tests = map {{'title' => $_}} grep {my $otest = basename($_); scalar(grep {basename($_->{'title'}) eq $otest} @tests) == 0} @realtests if $opts->{'no-match'}; #invert the list in this case.
  160. }
  161. @tests = map { abs_path($_->{'title'}) } @tests if $opts->{'match'} && $opts->{'names-only'};
  162. @tests = map { $_->{'full_title'} = abs_path($_->{'title'}); $_ } @tests if $opts->{'match'} && !$opts->{'names-only'};
  163. @tests = map { $_->{'title'} } @tests if !$opts->{'match'} && $opts->{'names-only'};
  164. return @tests;
  165. }
  166. 1;
  167. __END__
  168. =head1 SPECIAL THANKS
  169. Thanks to cPanel Inc, for graciously funding the creation of this module.