Jelajahi Sumber

Fix # 28: add testrail-runs utility

George S. Baugh 10 tahun lalu
induk
melakukan
aaf86088f8
6 mengubah file dengan 323 tambahan dan 6 penghapusan
  1. 2 1
      Changes
  2. 229 0
      bin/testrail-runs
  3. 11 4
      bin/testrail-tests
  4. 42 0
      lib/TestRail/API.pm
  5. 8 1
      t/TestRail-API.t
  6. 31 0
      t/testrail-runs.t

+ 2 - 1
Changes

@@ -1,11 +1,12 @@
 Revision history for Perl module TestRail::API
 
-0.021 2015-04-07 TEODESIAN
+0.021 2015-04-08 TEODESIAN
     - Fix issue where getChildRuns did not return anything past first run
     - Fix issue where getChildRunByName did not perform configuration filtering correctly
     - Add ability to filter by test status and assignedto id to getTests
     - Add bin/testrail-tests and bin/testrail-runs
     - Add statusNamesToIds and userNamesToIds convenience methods to TestRail::API
+    - Add getRunSummary to TestRail::API
 
 0.020 2015-03-25 TEODESIAN
     - Add getRunsPaginated and getPlansPaginated to get around 250 hardlimit in TR results

+ 229 - 0
bin/testrail-runs

@@ -0,0 +1,229 @@
+#!/usr/bin/env perl
+# ABSTRACT: List runs in a TestRail project matching the provided filters
+# PODNAME: testrail-runs
+
+=head1 SYNOPSIS
+
+  testrail-runs [OPTIONS] | xargs prove -PTestrail=...
+
+=head1 DESCRIPTION
+
+testrail-tests - list runs in a TestRail project matching the provided filters.
+Groups by plan for runs which are children of a plan.
+
+=head2 PARAMETERS:
+
+=head3 MANDATORY PARAMETERS
+
+    -j --project [project]: desired project name.
+
+=head3 OPTIONAL PARAMETERS
+
+    -c --config [config]: configuration name to filter runs.  Can be passed multiple times.
+    -s --status [status]: only list runs with one or more tests having [status] in testrail.  Can be passed multiple times.
+
+=head3 CONFIG OPTIONS
+
+    In your \$HOME, (or the current directory, if your system has no
+    concept of a home directory) put a file called .testrailrc with
+    key=value syntax separated by newlines.
+    Valid Keys are: apiurl,user,password
+
+=head3 CONFIG OVERRIDES
+
+    These override the config, if present.
+    If neither are used, you will be prompted.
+
+  --apiurl   [url] : full URL to get to TestRail index document
+  --password [key] : Your TestRail Password.
+  --user    [name] : Your TestRail User Name.
+
+=head2 TESTING OPTIONS:
+
+    --mock: don't do any real HTTP requests.
+    --help: show this output
+
+=cut
+
+use strict;
+use warnings;
+use utf8;
+
+use TestRail::API;
+use TestRail::Utils;
+
+use Getopt::Long;
+use File::HomeDir qw{my_home};
+use File::Find;
+use Cwd qw{abs_path};
+use File::Basename qw{basename};
+
+sub help {
+    print("
+testrail-tests - list tests in a run matching the provided filters.
+
+USAGE:
+
+  testrail-tests [OPTIONS] | xargs prove -PTestrail=...
+
+PARAMETERS:
+  [MANDATORY PARAMETERS]
+    -j --project [project]: desired project name.
+
+  [OPTIONAL PARAMETERS]
+
+    -c --config [config]: configuration name to filter runs.  Can be passed multiple times.
+    -s --status [status]: only list runs with one or more tests having [status] in testrail.  Can be passed multiple times.
+
+  [CONFIG OPTIONS]
+    In your \$HOME, (or the current directory, if your system has no
+    concept of a home directory) put a file called .testrailrc with
+    key=value syntax separated by newlines.
+    Valid Keys are: apiurl,user,password
+
+  [CONFIG OVERRIDES]
+    These override the config, if present.
+    If neither are used, you will be prompted.
+
+  --apiurl   [url] : full URL to get to TestRail index document
+  --password [key] : Your TestRail Password.
+  --user    [name] : Your TestRail User Name.
+
+TESTING OPTIONS:
+
+    --mock: don't do any real HTTP requests.
+    --help: show this output
+
+");
+    exit 0;
+}
+
+my %opts;
+
+GetOptions(
+    'apiurl'          => \$opts{'apiurl'},
+    'password'        => \$opts{'pass'},
+    'user'            => \$opts{'user'},
+    'j|project=s'     => \$opts{'project'},
+    'c|config=s@'     => \$opts{'configs'},
+    's|status=s@'     => \$opts{'statuses'},
+    'mock'            => \$opts{'mock'},
+    'h|help'          => \$opts{'help'}
+);
+
+if ($opts{help}) { help(); }
+
+#Parse config file if we are missing api url/key or user
+my $homedir = my_home() || '.';
+if (-e $homedir . '/.testrailrc' && (!$opts{apiurl} || !$opts{pass} || !$opts{user}) ) {
+    ($opts{apiurl},$opts{pass},$opts{user}) = TestRail::Utils::parseConfig($homedir);
+}
+
+#Interrogate user if they didn't provide info
+if (!$opts{apiurl}) {
+    print "Type the API endpoint url for your testLink install below:\n";
+    $opts{apiurl} = TestRail::Utils::userInput();
+}
+
+if (!$opts{user}) {
+    print "Type your testLink user name below:\n";
+    $opts{user} = TestRail::Utils::userInput();
+}
+
+if (!$opts{pass}) {
+    print "Type the password for your testLink user below:\n";
+    $opts{pass} = TestRail::Utils::userInput();
+}
+
+if (!$opts{apiurl} || !$opts{pass} || !$opts{user}) {
+    print "ERROR: api url, username and password cannot be blank.\n";
+    exit 1;
+}
+
+#Interrogate user if they didn't provide info
+if (!$opts{project}) {
+    print "Type the name of the project you are testing under:\n";
+    $opts{project} = TestRail::Utils::userInput();
+}
+
+if ($opts{mock}) {
+    use Test::LWP::UserAgent::TestRailMock;
+    $opts{browser} = $Test::LWP::UserAgent::TestRailMock::mockObject;
+    $opts{debug} = 1;
+}
+
+my $tr = TestRail::API->new($opts{apiurl},$opts{user},$opts{pass},$opts{'debug'});
+$tr->{'browser'} = $opts{'browser'} if $opts{'browser'};
+$tr->{'debug'} = 0;
+
+my ($status_ids,$user_ids);
+
+#Process statuses
+if ($opts{'statuses'}) {
+    eval { @$status_ids = $tr->statusNamesToIds(@{$opts{'statuses'}}); };
+    if ($@) {
+        print "$@\n";
+        exit 4;
+    }
+}
+
+my $project = $tr->getProjectByName($opts{'project'});
+if (!$project) {
+    print "No such project '$opts{project}'.\n";
+    exit 6;
+}
+
+my @pconfigs;
+if (defined $opts{configs}) {
+    my $avail_configs = $tr->getConfigurations($project->{'id'});
+    my ($cname);
+    @pconfigs = map {$_->{'id'}} grep { $cname = $_->{'name'}; grep {$_ eq $cname} @{$opts{configs}} } @$avail_configs; #Get a list of IDs from the names passed
+}
+
+if (defined($opts{configs}) && (scalar(@pconfigs) != scalar(@{$opts{configs}}))) {
+    print("One or more configurations passed does not exist in your project!\n");
+    exit 7;
+}
+
+my ($runs,$plans,$planRuns,$cruns,$found) = ([],[],[],[],0);
+$runs = $tr->getRuns($project->{'id'}) if (!$opts{'configs'}); # If configs are passed, global runs are not in consideration.
+$plans = $tr->getPlans($project->{'id'});
+foreach my $plan (@$plans) {
+    $cruns = $tr->getChildRuns($plan);
+    next if !$cruns;
+    foreach my $run (@$cruns) {
+        next if scalar(@pconfigs) != scalar(@{$run->{'config_ids'}});
+
+        #Compare run config IDs against desired, invalidate run if all conditions not satisfied
+        $found = 0;
+        foreach my $cid (@{$run->{'config_ids'}}) {
+            $found++ if grep {$_ == $cid} @pconfigs;
+        }
+
+        push(@$planRuns, $run) if $found == scalar(@{$run->{'config_ids'}});
+    }
+}
+
+push(@$runs,@$planRuns);
+
+if ($opts{'statuses'}) {
+    @$runs =  $tr->getRunSummary(@$runs);
+    @$runs = grep { defined($_->{'run_status'}) } @$runs; #Filter stuff with no results
+    foreach my $status (@{$opts{'statuses'}}) {
+        @$runs = grep { $_->{'run_status'}->{$status} } @$runs; #If it's positive, keep it.  Otherwise forget it.
+    }
+}
+
+@$runs = map {$_->{name}} @$runs;
+print join("\n",@$runs)."\n" if scalar(@$runs);
+exit 0;
+
+__END__
+
+L<TestRail::API>
+
+L<File::HomeDir> for the finding of .testrailrc
+
+=head1 SPECIAL THANKS
+
+Thanks to cPanel Inc, for graciously funding the creation of this module.

+ 11 - 4
bin/testrail-tests

@@ -1,6 +1,6 @@
 #!/usr/bin/env perl
 # ABSTRACT: List tests in a TestRail run matching the provided filters
-# PODNAME: testrail-test
+# PODNAME: testrail-tests
 
 =head1 SYNOPSIS
 
@@ -20,6 +20,8 @@ testrail-tests - list tests in a run matching the provided filters.
 =head3 SEMI-OPTIONAL PARAMETERS
 
     -p --plan [plan]: desired plan name.  Required if the run passed is a child of a plan.
+    -m --match [dir]: attempt to find filenames matching the test names in the provided dir.
+    -n --no-recurse: if match passed, do not recurse subdirectories.
 
 =head3 OPTIONAL PARAMETERS
 
@@ -45,7 +47,8 @@ testrail-tests - list tests in a run matching the provided filters.
 
 =head2 TESTING OPTIONS:
 
-    --mock don't do any real HTTP requests.
+    --mock: don't do any real HTTP requests.
+    --help: show this output
 
 =cut
 
@@ -103,8 +106,8 @@ PARAMETERS:
 
 TESTING OPTIONS:
 
-    --mock don't do any real HTTP requests.
-
+    --mock: don't do any real HTTP requests.
+    --help: show this output
 ");
     exit 0;
 }
@@ -178,6 +181,10 @@ $tr->{'browser'} = $opts{'browser'} if $opts{'browser'};
 $tr->{'debug'} = 0;
 
 my $project = $tr->getProjectByName($opts{'project'});
+if (!$project) {
+    print "No such project '$opts{project}'.\n";
+    exit 6;
+}
 
 my ($run,$plan);
 

+ 42 - 0
lib/TestRail/API.pm

@@ -1183,6 +1183,48 @@ sub getRunByID {
     return $self->_doRequest("index.php?/api/v2/get_run/$run_id");
 }
 
+=head2 B<getRunSummary(runs)>
+
+Returns array of hashrefs describing the # of tests in the run(s) with the available statuses.
+Translates custom_statuses into their system names for you.
+
+=over 4
+
+=item ARRAY C<RUNS> - runs obtained from getRun* or getChildRun* methods.
+
+=back
+
+Returns ARRAY of run HASHREFs with the added key 'run_status' holding a hashref where status_name => count.
+
+    $tr->getRunSummary($run,$run2);
+
+=cut
+
+sub getRunSummary {
+    my ($self,@runs) = @_;
+
+    #Translate custom statuses
+    my $statuses = $self->getPossibleTestStatuses();
+    my %shash;
+    #XXX so, they do these tricks with the status names, see...so map the counts to their relevant status ids.
+    @shash{map { ( $_->{'id'} < 6 ) ? $_->{'name'}."_count" : "custom_status".($_->{'id'} - 5)."_count" } @$statuses } = map { $_->{'id'} } @$statuses;
+    my @sname;
+    #Create listing of keys/values
+    @runs = map {
+        my $run = $_;
+        @{$run->{statuses}}{grep {$_ =~ m/_count$/} keys($run)} = grep {$_ =~ m/_count$/} keys($run);
+        foreach my $status (keys($run->{'statuses'})) {
+            next if !exists($shash{$status});
+            @sname = grep {exists($shash{$status}) && $_->{'id'} == $shash{$status}} @$statuses;
+            $run->{'statuses_clean'}->{$sname[0]->{'name'}} = $run->{$status};
+        }
+        $run;
+    } @runs;
+
+    return map { {'id' => $_->{'id'}, 'name' => $_->{'name'}, 'run_status' => $_->{'statuses_clean'}} } @runs;
+
+}
+
 =head1 RUN AS CHILD OF PLAN METHODS
 
 =head2 B<getChildRuns(plan)>

+ 8 - 1
t/TestRail-API.t

@@ -4,7 +4,7 @@ use warnings;
 use TestRail::API;
 use Test::LWP::UserAgent::TestRailMock;
 
-use Test::More tests => 68;
+use Test::More tests => 71;
 use Test::Fatal;
 use Test::Deep;
 use Scalar::Util 'reftype';
@@ -167,6 +167,13 @@ $filteredTests = $tr->getTests($new_run->{'id'},undef,[$userlist->[0]->{'id'}]);
 is(scalar(@$filteredTests),0,"Test Filtering works: status id undef, user id negative");
 #XXX there is no way to programmatically assign things :( so this will remain somewhat uncovered
 
+#Get run summary
+my $runs = $tr->getRuns($new_project->{'id'});
+my ($summary) = $tr->getRunSummary(@$runs); #I only care about the first one
+isnt($summary->{'run_status'},undef,"Can get run statuses correctly");
+is($summary->{'run_status'}->{'passed'},int(!$is_mock),"Gets # of passed cases correctly");
+is($summary->{'run_status'}->{'untested'},int($is_mock),"Gets # of untested cases correctly");
+
 #Test configuration methods
 my $configs = $tr->getConfigurations($new_project->{'id'});
 my $is_arr = is(reftype($configs),'ARRAY',"Can get configurations for a project");

+ 31 - 0
t/testrail-runs.t

@@ -0,0 +1,31 @@
+use strict;
+use warnings;
+
+use Test::More 'tests' => 8;
+
+#check status filters
+my @args = ($^X,qw{bin/testrail-runs -j 'TestProject' --mock});
+my $out = `@args`;
+is($? >> 8, 0, "Exit code OK looking for runs with passes");
+chomp $out;
+like($out,qr/^TestingSuite\nOtherOtherSuite$/,"Gets run correctly looking for passes");
+
+#check status filters
+@args = ($^X,qw{bin/testrail-runs -j 'TestProject' --mock --status passed});
+$out = `@args`;
+is($? >> 8, 0, "Exit code OK looking for runs with passes, which should fail to return results");
+chomp $out;
+is($out,'',"Gets no runs correctly looking for passes");
+
+@args = ($^X,qw{bin/testrail-runs -j 'CRUSH ALL HUMANS' --mock --status passed});
+$out = `@args`;
+is($? >> 8, 0, "Exit code OK looking for runs with passes");
+chomp $out;
+like($out,qr/SEND T-1000 INFILTRATION UNITS BACK IN TIME$/,"Gets run correctly looking for passes");
+
+#TODO check configs for real next time
+@args = ($^X,qw{bin/testrail-runs -j 'TestProject' --mock --config testConfig});
+$out = `@args`;
+is($? >> 8, 0, "Exit code OK looking for runs with passes");
+chomp $out;
+is($out,'',"Gets no run correctly when filtering by unassigned config");