Quellcode durchsuchen

Fix #37: Add ability to discriminate by section name to TR parser

George S. Baugh vor 10 Jahren
Ursprung
Commit
6aeda20ca9

+ 3 - 0
Changes

@@ -3,6 +3,9 @@ Revision history for Perl module TestRail::API
 0.026 2015-06-?? TEODESIAN
     - Add --no-match option to testrail-tests to find orphan tests in a tree
     - Upload full raw results to TestRail when not in step_results or case_per_ok mode
+    - Add ability to pass section IDs to discriminate when spawning runs in Test::Rail::Parser
+    - Add ability to pass section names to App::Prove::Plugin::TestRail and testrail-report when spawning runs.
+    - Add sectionNamesToIds convenience method to TestRail::API
 
 0.025 2015-05-21 TEODESIAN
     - Fix test failures on windows (and an issue in testrail-tests on win32)

+ 12 - 1
bin/testrail-report

@@ -48,6 +48,9 @@ This should provide sufficient uniqueness to get any run using names.
       If the specified run already exists, the program will simply use the existing run, and disregard the supplied testsuite_id.
       If the specified plan does not exist, it too will be created for you.
 
+    --section [section_name] : When spawning, restrict the cases used in the provided testsuite ID to these sections.
+      Option may be passed multiple times to specify multiple sections.
+
 =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
@@ -168,6 +171,12 @@ PARAMETERS:
       If the specified plan does not exist, it will be created
       as well.
 
+    --section [section_name] : When spawning, restrict the cases used
+      in the provided testsuite ID to these sections.
+
+      Option may be passed multiple times to specify multiple sections.
+
+
   [CONFIG OVERRIDES]
   In your \$HOME, (or the current directory, if your system has no
   concept of a home directory) put a file called .testrailrc with
@@ -218,7 +227,7 @@ TESTING OPTIONS:
 
 #Main loop------------
 
-my ($help,$apiurl,$user,$password,$project,$run,$case_per_ok,$step_results,$mock,$configs,$plan,$version,$spawn);
+my ($help,$apiurl,$user,$password,$project,$run,$case_per_ok,$step_results,$mock,$configs,$plan,$version,$spawn,$sections);
 
 #parse switches
 GetOptions(
@@ -234,6 +243,7 @@ GetOptions(
     'plan=s'         => \$plan,
     'version=s'      => \$version,
     'spawn=i'        => \$spawn,
+    'section=s@'     => \$sections,
     'help'           => \$help
 );
 
@@ -353,6 +363,7 @@ foreach my $phil (@files) {
         'configs'      => $configs,
         'result_options' => $result_options,
         'spawn'        => $spawn,
+        'sections'     => $sections,
         'merge'        => 1
     });
     $tap->run();

+ 13 - 11
lib/App/Prove/Plugin/TestRail.pm

@@ -35,6 +35,7 @@ If \$HOME/.testrailrc exists, it will be parsed for any of these values in a new
     case_per_ok=0
     step_results=sr_sys_name
     spawn=123
+    sections=section1:section2:section3: ... :sectionN
 
 Note that passing configurations as filters for runs inside of plans are separated by colons.
 Values passed in via query string will override values in \$HOME/.testrailrc.
@@ -75,17 +76,18 @@ sub load {
     $app->merge(1);
 
     #XXX I can't figure out for the life of me any other way to pass this data. #YOLO
-    $ENV{'TESTRAIL_APIURL'}  = $params->{apiurl};
-    $ENV{'TESTRAIL_USER'}    = $params->{user};
-    $ENV{'TESTRAIL_PASS'}    = $params->{password};
-    $ENV{'TESTRAIL_PROJ'}    = $params->{project};
-    $ENV{'TESTRAIL_RUN'}     = $params->{run};
-    $ENV{'TESTRAIL_PLAN'}    = $params->{plan};
-    $ENV{'TESTRAIL_CONFIGS'} = $params->{configs};
-    $ENV{'TESTRAIL_VERSION'} = $params->{version};
-    $ENV{'TESTRAIL_CASEOK'}  = $params->{case_per_ok};
-    $ENV{'TESTRAIL_STEPS'}   = $params->{step_results};
-    $ENV{'TESTRAIL_SPAWN'}   = $params->{spawn};
+    $ENV{'TESTRAIL_APIURL'}   = $params->{apiurl};
+    $ENV{'TESTRAIL_USER'}     = $params->{user};
+    $ENV{'TESTRAIL_PASS'}     = $params->{password};
+    $ENV{'TESTRAIL_PROJ'}     = $params->{project};
+    $ENV{'TESTRAIL_RUN'}      = $params->{run};
+    $ENV{'TESTRAIL_PLAN'}     = $params->{plan};
+    $ENV{'TESTRAIL_CONFIGS'}  = $params->{configs};
+    $ENV{'TESTRAIL_VERSION'}  = $params->{version};
+    $ENV{'TESTRAIL_CASEOK'}   = $params->{case_per_ok};
+    $ENV{'TESTRAIL_STEPS'}    = $params->{step_results};
+    $ENV{'TESTRAIL_SPAWN'}    = $params->{spawn};
+    $ENV{'TESTRAIL_SECTIONS'} = $params->{sections};
     return $class;
 }
 

+ 28 - 27
lib/Test/LWP/UserAgent/TestRailMock.pm

@@ -229,33 +229,6 @@ $mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4,
 
 {
 
-$VAR1 = 'index.php?/api/v2/get_suites/9';
-$VAR2 = '200';
-$VAR3 = 'OK';
-$VAR4 = bless( {
-                 'connection' => 'close',
-                 'x-powered-by' => 'PHP/5.5.9-1ubuntu4.5',
-                 'client-response-num' => 1,
-                 'date' => 'Tue, 23 Dec 2014 20:02:08 GMT',
-                 'client-peer' => '192.168.122.217:80',
-                 'content-length' => '256',
-                 '::std_case' => {
-                                   'client-date' => 'Client-Date',
-                                   'x-powered-by' => 'X-Powered-By',
-                                   'client-response-num' => 'Client-Response-Num',
-                                   'client-peer' => 'Client-Peer'
-                                 },
-                 'client-date' => 'Tue, 23 Dec 2014 20:02:08 GMT',
-                 'content-type' => 'application/json; charset=utf-8',
-                 'server' => 'Apache/2.4.7 (Ubuntu)'
-               }, 'HTTP::Headers' );
-$VAR5 = '[{"id":9,"name":"HAMBURGER-IZE HUMANITY","description":"Robo-Signed Patriotic People\'s TestSuite","project_id":9,"is_master":false,"is_baseline":false,"is_completed":false,"completed_on":null,"url":"http:\\/\\/testrail.local\\/\\/index.php?\\/suites\\/view\\/9"}]';
-$mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4, $VAR5));
-
-}
-
-{
-
 $VAR1 = 'index.php?/api/v2/get_suite/9';
 $VAR2 = '200';
 $VAR3 = 'OK';
@@ -498,6 +471,34 @@ $mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4,
 
 }
 
+{
+
+$VAR1 = 'index.php?/api/v2/get_cases/10&suite_id=9&section_id=9';
+$VAR2 = '200';
+$VAR3 = 'OK';
+$VAR4 = bless( {
+                 'connection' => 'close',
+                 'x-powered-by' => 'PHP/5.5.9-1ubuntu4.5',
+                 'client-response-num' => 1,
+                 'date' => 'Tue, 23 Dec 2014 20:02:09 GMT',
+                 'client-peer' => '192.168.122.217:80',
+                 'content-length' => '322',
+                 '::std_case' => {
+                                   'client-date' => 'Client-Date',
+                                   'x-powered-by' => 'X-Powered-By',
+                                   'client-response-num' => 'Client-Response-Num',
+                                   'client-peer' => 'Client-Peer'
+                                 },
+                 'client-date' => 'Tue, 23 Dec 2014 20:02:09 GMT',
+                 'content-type' => 'application/json; charset=utf-8',
+                 'server' => 'Apache/2.4.7 (Ubuntu)'
+               }, 'HTTP::Headers' );
+$VAR5 = '[{"id":8,"title":"STROGGIFY POPULATION CENTERS","section_id":9,"type_id":6,"priority_id":4,"milestone_id":null,"refs":null,"created_by":1,"created_on":1419364929,"updated_by":1,"updated_on":1419364929,"estimate":null,"estimate_forecast":null,"suite_id":9,"custom_preconds":null,"custom_steps":null,"custom_expected":null}]';
+$mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4, $VAR5));
+
+}
+
+
 {
 
 $VAR1 = 'index.php?/api/v2/get_case/8';

+ 4 - 1
lib/Test/Rail/Harness.pm

@@ -40,6 +40,7 @@ sub make_parser {
     my ($self, $job) = @_;
     my $args = $self->SUPER::_get_parser_args($job);
     my @configs = ();
+    my @sections = ();
 
     #XXX again, don't see any way of getting this downrange to my parser :(
     $args->{'apiurl'}  = $ENV{'TESTRAIL_APIURL'};
@@ -49,11 +50,13 @@ sub make_parser {
     $args->{'run'}     = $ENV{'TESTRAIL_RUN'};
     $args->{'plan'}    = $ENV{'TESTRAIL_PLAN'};
     @configs = split(/:/,$ENV{'TESTRAIL_CONFIGS'}) if $ENV{'TESTRAIL_CONFIGS'};
-    $args->{'configs'} = \@configs;
+    $args->{'configs'} = \@configs if scalar(@configs);
     $args->{'result_options'} = {'version' => $ENV{'TESTRAIL_VERSION'}} if $ENV{'TESTRAIL_VERSION'};
     $args->{'case_per_ok'}    = $ENV{'TESTRAIL_CASEOK'};
     $args->{'step_results'}   = $ENV{'TESTRAIL_STEPS'};
     $args->{'spawn'}          = $ENV{'TESTRAIL_SPAWN'};
+    @sections = split(/:/,$ENV{'TESTRAIL_SECTIONS'}) if $ENV{'TESTRAIL_SECTIONS'};
+    $args->{'sections'} = \@sections if scalar(@sections);
 
     #for Testability of plugin
     if ($ENV{'TESTRAIL_MOCKED'}) {

+ 39 - 16
lib/Test/Rail/Parser.pm

@@ -74,6 +74,8 @@ Get the TAP Parser ready to talk to TestRail, and register a bunch of callbacks
 
 =item B<spawn> - INTEGER (optional): Attempt to create a run based on the provided testsuite identified by the ID passed here.  If plan/configs is passed, create it as a child of said plan with the listed configs.  If the run exists, use it and disregard the provided testsuite ID.  If the plan does not exist, create it too.
 
+=item B<sections> - ARRAYREF (optional): Restrict a spawned run to cases in these particular sections.
+
 =back
 
 =back
@@ -112,6 +114,7 @@ sub new {
         'plan'         => delete $opts->{'plan'},
         'configs'      => delete $opts->{'configs'} // [],
         'spawn'        => delete $opts->{'spawn'},
+        'sections'     => delete $opts->{'sections'},
         #Stubs for extension by subclassers
         'result_options'        => delete $opts->{'result_options'},
         'result_custom_options' => delete $opts->{'result_custom_options'}
@@ -197,21 +200,41 @@ sub new {
 
     #If spawn was passed and we don't have a Run ID yet, go ahead and make it
     if ($tropts->{'spawn'} && !$tropts->{'run_id'}) {
+        print "# Spawning run\n";
+        my $cases = [];
+        if ($tropts->{'sections'}) {
+            print "# with specified sections\n";
+            #Then translate the sections into an array of case IDs.
+            confess("Sections passed to spawn must be ARRAYREF") unless (reftype($tropts->{'sections'}) || 'undef') eq 'ARRAY';
+            @{$tropts->{'sections'}} = $tr->sectionNamesToIds($tropts->{'project_id'},$tropts->{'spawn'},@{$tropts->{'sections'}});
+            foreach my $section (@{$tropts->{'sections'}}) {
+                my $cases = $tr->getCases($tropts->{'project_id'},$tropts->{'spawn'},$section);
+                push(@$cases,@$cases) if (reftype($cases) || 'undef') eq 'ARRAY';
+            }
+        }
+        if (scalar(@$cases)) {
+            @$cases = map {$_->{'id'}} @$cases;
+        } else {
+            $cases = undef;
+        }
+
         if ($tropts->{'plan'}) {
-            $plan = $tr->createRunInPlan( $tropts->{'plan'}->{'id'}, $tropts->{'spawn'}, $tropts->{'run'}, undef, $config_ids );
+            print "# inside of plan\n";
+            $plan = $tr->createRunInPlan( $tropts->{'plan'}->{'id'}, $tropts->{'spawn'}, $tropts->{'run'}, undef, $config_ids, $cases );
             $run = $plan->{'runs'}->[0] if exists($plan->{'runs'}) && (reftype($plan->{'runs'}) || 'undef') eq 'ARRAY' && scalar(@{$plan->{'runs'}});
             if (defined($run) && (reftype($run) || 'undef') eq 'HASH') {
                 $tropts->{'run'} = $run;
                 $tropts->{'run_id'} = $run->{'id'};
             }
         } else {
-            $run = $tr->createRun( $tropts->{'project_id'}, $tropts->{'spawn'}, $tropts->{'run'}, "Automatically created Run from TestRail::API" );
+            $run = $tr->createRun( $tropts->{'project_id'}, $tropts->{'spawn'}, $tropts->{'run'}, "Automatically created Run from TestRail::API", undef, undef, $cases );
             if (defined($run) && (reftype($run) || 'undef') eq 'HASH') {
                 $tropts->{'run'} = $run;
                 $tropts->{'run_id'} = $run->{'id'};
             }
         }
         confess("Could not spawn run with requested parameters!") if !$tropts->{'run_id'};
+        print "# Success!\n"
     }
 
     confess("No run ID provided, and no run with specified name exists in provided project/plan!") if !$tropts->{'run_id'};
@@ -219,7 +242,7 @@ sub new {
     $self = $class->SUPER::new($opts);
     if (defined($self->{'_iterator'}->{'command'}) && reftype($self->{'_iterator'}->{'command'}) eq 'ARRAY' ) {
         $self->{'file'} = $self->{'_iterator'}->{'command'}->[-1];
-        print "PROCESSING RESULTS FROM TEST FILE: $self->{'file'}\n";
+        print "# PROCESSING RESULTS FROM TEST FILE: $self->{'file'}\n";
         $self->{'track_time'} = 1;
     } else {
         #Not running inside of prove in real-time, don't bother with tracking elapsed times.
@@ -260,13 +283,13 @@ sub unknownCallback {
     if ($line =~ /^Running\s(.*)/) {
         #TODO figure out which testsuite this implies
         $self->{'file'} = $1;
-        print "PROCESSING RESULTS FROM TEST FILE: $self->{'file'}\n";
+        print "# PROCESSING RESULTS FROM TEST FILE: $self->{'file'}\n";
     }
     #RAW tap #XXX this regex could be improved
     if ($line =~ /(.*)\s\.\.\s*$/) {
         $self->{'file'} = $1 unless $line =~ /^[ok|not ok] - /; #a little more careful
     }
-    print "$line\n" if ($line =~ /^error/i);
+    print "# $line\n" if ($line =~ /^error/i);
 }
 
 =head2 commentCallback
@@ -313,7 +336,7 @@ sub testCallback {
 
     #Don't do anything if we don't want to map TR case => ok or use step-by-step results
     if ( !($self->{'tr_opts'}->{'step_results'} || $self->{'tr_opts'}->{'case_per_ok'}) ) {
-        print "Neither step_results of case_per_ok set.  No action to be taken, except on a whole test basis.\n" if $self->{'tr_opts'}->{'debug'};
+        print "# Neither step_results of case_per_ok set.  No action to be taken, except on a whole test basis.\n" if $self->{'tr_opts'}->{'debug'};
         return 1;
     }
     if ($self->{'tr_opts'}->{'step_results'} && $self->{'tr_opts'}->{'case_per_ok'}) {
@@ -336,7 +359,7 @@ sub testCallback {
     my $test_name  = $line;
     my $run_id     = $self->{'tr_opts'}->{'run_id'};
 
-    print "Assuming test name is '$test_name'...\n" if $self->{'tr_opts'}->{'debug'} && !$self->{'tr_opts'}->{'step_results'};
+    print "# Assuming test name is '$test_name'...\n" if $self->{'tr_opts'}->{'debug'} && !$self->{'tr_opts'}->{'step_results'};
 
     my $todo_reason;
     #Setup args to pass to function
@@ -377,7 +400,7 @@ sub testCallback {
             @{$self->{'tr_opts'}->{'result_custom_options'}->{'step_results'}},
             TestRail::API::buildStepResults($line,"Good result","Bad Result",$status)
         );
-        print "Appended step results.\n" if $self->{'tr_opts'}->{'debug'};
+        print "# Appended step results.\n" if $self->{'tr_opts'}->{'debug'};
         return 1;
     }
 
@@ -413,7 +436,7 @@ sub EOFCallback {
     }
 
     if ($self->{'tr_opts'}->{'case_per_ok'}) {
-        print "Nothing left to do.\n";
+        print "# Nothing left to do.\n";
         undef $self->{'tr_opts'} unless $self->{'tr_opts'}->{'debug'};
         return 1;
     }
@@ -439,7 +462,7 @@ sub EOFCallback {
     my $custom_options = $self->{'tr_opts'}->{'result_custom_options'};
 
 
-    print "Setting results...\n";
+    print "# Setting results...\n";
     my $cres = _set_result($run_id,$test_name,$status,$notes,$options,$custom_options);
 
     undef $self->{'tr_opts'} unless $self->{'tr_opts'}->{'debug'};
@@ -452,9 +475,9 @@ sub _set_result {
     our $self;
     my $tc;
 
-    print "Test elapsed: ".$options->{'elapsed'}."\n" if $options->{'elapsed'};
+    print "# Test elapsed: ".$options->{'elapsed'}."\n" if $options->{'elapsed'};
 
-    print "Attempting to find case by title '".$test_name."'...\n";
+    print "# Attempting to find case by title '".$test_name."'...\n";
     $tc = $self->{'tr_opts'}->{'testrail'}->getTestByName($run_id,$test_name);
     if (!defined($tc) || (reftype($tc) || 'undef') ne 'HASH') {
         cluck("ERROR: Could not find test case: $tc");
@@ -467,14 +490,14 @@ sub _set_result {
 
     #Set test result
     if ($tc) {
-        print "Reporting result of case $xid in run $self->{'tr_opts'}->{'run_id'} as status '$status'...";
+        print "# Reporting result of case $xid in run $self->{'tr_opts'}->{'run_id'} as status '$status'...";
         # createTestResults(test_id,status_id,comment,options,custom_options)
         $cres = $self->{'tr_opts'}->{'testrail'}->createTestResults($tc->{'id'},$status, $notes, $options, $custom_options);
-        print "OK! (set to $status)\n" if (reftype($cres) || 'undef') eq 'HASH';
+        print "# OK! (set to $status)\n" if (reftype($cres) || 'undef') eq 'HASH';
     }
     if (!$tc || ((reftype($cres) || 'undef') ne 'HASH') ) {
-        print "Failed!\n";
-        print "No Such test case in TestRail ($xid).\n";
+        print "# Failed!\n";
+        print "# No Such test case in TestRail ($xid).\n";
         $self->{'errors'}++;
     }
 

+ 35 - 1
lib/TestRail/API.pm

@@ -746,6 +746,40 @@ sub getSectionByName {
     return 0;
 }
 
+=head2 sectionNamesToIds(project_id,suite_id,names)
+
+Convenience method to translate a list of section names to TestRail section IDs.
+
+=over 4
+
+=item INTEGER C<PROJECT ID> - ID of parent project.
+
+=item INTEGER C<SUITE ID> - ID of parent suite.
+
+=item ARRAY C<NAMES> - Array of section names to translate to IDs.
+
+=back
+
+Returns ARRAY of section IDs.
+
+Throws an exception in the case of one (or more) of the names not corresponding to a valid section name.
+
+=cut
+
+sub sectionNamesToIds {
+    my ($self,$project_id,$suite_id,@names) = @_;
+    confess("Object methods must be called by an instance") unless ref($self);
+    confess("Project ID must be an integer") unless $self->_checkInteger($project_id);
+    confess("Suite ID must be an integer") unless $self->_checkInteger($suite_id);
+    confess("At least one section name must be provided") if !scalar(@names);
+
+    my $sections = $self->getSections($project_id,$suite_id);
+    confess("Invalid project/suite ($project_id,$suite_id) provided.") unless (reftype($sections) || 'undef') eq 'ARRAY';
+    my @ret = grep {defined $_} map {my $section = $_; my @list = grep {$section->{'name'} eq $_} @names; scalar(@list) ? $section->{'id'} : undef} @$sections;
+    confess("One or more user names provided does not exist in TestRail.") unless scalar(@names) == scalar(@ret);
+    return @ret;
+}
+
 =head1 CASE METHODS
 
 =head2 B<getCaseTypes ()>
@@ -1511,7 +1545,7 @@ sub getPlanByID {
     return $self->_doRequest("index.php?/api/v2/get_plan/$plan_id");
 }
 
-=head2 B<createRunInPlan (plan_id,suite_id,name,description,milestone_id,assigned_to_id,case_ids)>
+=head2 B<createRunInPlan (plan_id,suite_id,name,description,milestone_id,assigned_to_id,config_ids,case_ids)>
 
 Create a run.
 

+ 8 - 1
t/App-Prove-Plugin-Testrail.t

@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More 'tests' => 4;
+use Test::More 'tests' => 5;
 use Test::Fatal;
 use App::Prove;
 use App::Prove::Plugin::TestRail;
@@ -34,3 +34,10 @@ $prove = App::Prove->new();
 $prove->process_args("-PTestRail=apiurl=http://some.testlink.install/,user=someUser,password=somePassword,project=TestProject,plan=bogoPlan,run=bogoRun,version=0.014,case_per_ok=1,spawn=9",'t/skipall.test');
 
 is (exception {$prove->run()},undef,"Running TR parser spawns both runs and plans");
+
+$prove = App::Prove->new();
+$prove->process_args("-PTestRail=apiurl=http://some.testlink.install/,user=someUser,password=somePassword,project=TestProject,run=bogoRun,version=0.014,case_per_ok=1,spawn=9,sections=fake.test:CARBON LIQUEFACTION",'t/fake.test');
+
+is (exception {$prove->run()},undef,"Running TR parser can discriminate by sections correctly");
+
+

+ 73 - 1
t/Test-Rail-Parser.t

@@ -7,7 +7,7 @@ use Scalar::Util qw{reftype};
 use TestRail::API;
 use Test::LWP::UserAgent::TestRailMock;
 use Test::Rail::Parser;
-use Test::More 'tests' => 41;
+use Test::More 'tests' => 48;
 use Test::Fatal qw{exception};
 
 #Same song and dance as in TestRail-API.t
@@ -375,4 +375,76 @@ $res = exception {
 };
 isnt($res,undef,"TR Parser explodes on instantiation when mutually exclusive options are passed");
 
+#Check that per-section spawn works
+undef $tap;
+$res = exception {
+    $tap = Test::Rail::Parser->new({
+        'source'              => 't/fake.test',
+        'apiurl'              => $apiurl,
+        'user'                => $login,
+        'pass'                => $pw,
+        'debug'               => $debug,
+        'browser'             => $browser,
+        'run'                 => 'BogoRun',
+        'project'             => 'TestProject',
+        'merge'               => 1,
+        'spawn'               => 9,
+        'sections'            => ['fake.test'],
+        'case_per_ok'         => 1
+    });
+};
+is($res,undef,"TR Parser doesn't explode on instantiation");
+isa_ok($tap,"Test::Rail::Parser");
+
+if (!$res) {
+    $tap->run();
+    is($tap->{'errors'},0,"No errors encountered uploading case results");
+}
+
+#Check that per-section spawn works
+undef $tap;
+$res = exception {
+    $tap = Test::Rail::Parser->new({
+        'source'              => 't/fake.test',
+        'apiurl'              => $apiurl,
+        'user'                => $login,
+        'pass'                => $pw,
+        'debug'               => $debug,
+        'browser'             => $browser,
+        'run'                 => 'BogoRun',
+        'plan'                => 'BogoPlan',
+        'project'             => 'TestProject',
+        'merge'               => 1,
+        'spawn'               => 9,
+        'sections'            => ['fake.test'],
+        'case_per_ok'         => 1
+    });
+};
+is($res,undef,"TR Parser doesn't explode on instantiation");
+isa_ok($tap,"Test::Rail::Parser");
+
+if (!$res) {
+    $tap->run();
+    is($tap->{'errors'},0,"No errors encountered uploading case results");
+}
+
+undef $tap;
+$res = exception {
+    $tap = Test::Rail::Parser->new({
+        'source'              => 't/fake.test',
+        'apiurl'              => $apiurl,
+        'user'                => $login,
+        'pass'                => $pw,
+        'debug'               => $debug,
+        'browser'             => $browser,
+        'run'                 => 'BogoRun',
+        'project'             => 'TestProject',
+        'merge'               => 1,
+        'spawn'               => 9,
+        'sections'            => ['potzrebie'],
+        'case_per_ok'         => 1
+    });
+};
+isnt($res,undef,"TR Parser explodes on instantiation with invalid section");
+
 0;

+ 5 - 2
t/TestRail-API.t

@@ -4,7 +4,7 @@ use warnings;
 use TestRail::API;
 use Test::LWP::UserAgent::TestRailMock;
 
-use Test::More tests => 71;
+use Test::More tests => 73;
 use Test::Fatal;
 use Test::Deep;
 use Scalar::Util ();
@@ -56,7 +56,6 @@ my @cuser_ids = $tr->userNamesToIds(@user_names);
 cmp_deeply(\@cuser_ids,\@user_ids,"userNamesToIds functions correctly");
 isnt(exception {$tr->userNamesToIds(@user_names,'potzrebie'); }, undef, "Passing invalid user name throws exception");
 
-
 #Test PROJECT methods
 my $project_name = 'CRUSH ALL HUMANS';
 
@@ -86,6 +85,10 @@ ok($tr->getSections($new_project->{'id'},$new_suite->{'id'}),"Can get section li
 is($tr->getSectionByName($new_project->{'id'},$new_suite->{'id'},$section_name)->{'name'},$section_name,"Can get section by name");
 is($tr->getSectionByID($new_section->{'id'})->{'id'},$new_section->{'id'},"Can get new section by id");
 
+my @cids = $tr->sectionNamesToIds($new_project->{'id'},$new_suite->{'id'},$section_name);
+is($cids[0],$new_section->{'id'},"sectionNamesToIds returns correct IDs");
+isnt(exception {$tr->sectionNamesToIds($new_project->{'id'},$new_suite->{'id'},"No such Section");},undef,"Passing bogus section to sectionNamesToIds throws exception");
+
 #Test CASE methods
 my $case_name = 'STROGGIFY POPULATION CENTERS';
 my $new_case = $tr->createCase($new_section->{'id'},$case_name);

+ 4 - 1
t/arg_types.t

@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 use TestRail::API;
-use Test::More 'tests' => 135;
+use Test::More 'tests' => 138;
 use Test::Fatal;
 use Class::Inspector;
 use Test::LWP::UserAgent;
@@ -48,6 +48,7 @@ isnt( exception {$tr->getRunByID() },undef,'getRunByID returns error when no arg
 isnt( exception {$tr->getRunByName() },undef,'getRunByName returns error when no arguments are passed');
 isnt( exception {$tr->getSectionByID() },undef,'getSectionByID returns error when no arguments are passed');
 isnt( exception {$tr->getSectionByName() },undef,'getSectionByName returns error when no arguments are passed');
+isnt( exception {$tr->sectionNamesToIds() },undef,'sectionNamesToIds returns error when no arguments are passed');
 isnt( exception {$tr->getTestByID() },undef,'getTestByID returns error when no arguments are passed');
 isnt( exception {$tr->getTestByName() },undef,'getTestByName returns error when no arguments are passed');
 isnt( exception {$tr->getTestResults() },undef,'getTestResults returns error when no arguments are passed');
@@ -121,6 +122,7 @@ isnt(exception {$tr->getPlanByName(1)}, undef,'getPlanByName with 1 arg returns
 isnt(exception {$tr->getRunByName(1)}, undef,'getRunByName with 1 arg returns error');
 isnt(exception {$tr->getSectionByName(1)}, undef,'getSectionByName with 1 arg returns error');
 isnt(exception {$tr->getSections(1)}, undef,'getSections with 1 arg returns error');
+isnt(exception {$tr->sectionNamesToIds(1) },undef,'sectionNamesToIds returns error when 1 arguments are passed');
 isnt(exception {$tr->getTestByName(1)}, undef,'getTestByName with 1 arg returns error');
 isnt(exception {$tr->getTestSuiteByName(1)}, undef,'getTestSuiteByName with 1 arg returns error');
 isnt(exception {$tr->getChildRunByName({}) },undef,'getChildRunByName returns error when 1 argument passed');
@@ -147,6 +149,7 @@ isnt(exception {$tr->createSection(1,1)}, undef,'createSection with 2 args retur
 isnt(exception {$tr->getCaseByName(1,1)}, undef,'getCaseByName with 2 args returns error');
 isnt(exception {$tr->getCases(1,2)}, undef,'getCases with 2 args returns error');
 isnt(exception {$tr->getSectionByName(1,1)}, undef,'getSectionByName with 2 args returns error');
+isnt(exception {$tr->sectionNamesToIds(1,1) },undef,'sectionNamesToIds returns error when 2 arguments are passed');
 isnt( exception {$tr->createRunInPlan(1,1) },undef,'createRunInPlan returns error when 2 arguments passed');
 
 #3 arg functions

+ 8 - 1
t/testrail-report.t

@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use Test::More 'tests' => 12;
+use Test::More 'tests' => 14;
 
 my @args = ($^X,qw{bin/testrail-report --apiurl http://testrail.local --user "test@fake.fake" --password "fake" --project "CRUSH ALL HUMANS" --run "SEND T-1000 INFILTRATION UNITS BACK IN TIME" --mock t/test_multiple_files.tap});
 my $out = `@args`;
@@ -36,6 +36,13 @@ is($? >> 8, 0, "Exit code OK reported with spawn");
 $matches = () = $out =~ m/Reporting result of case.*OK/ig;
 is($matches,2,"Attempts to spawn work");
 
+#Test that spawn works w/sections
+@args = ($^X,qw{bin/testrail-report --apiurl http://testrail.local --user "test@fake.fake" --password "fake" --project "TestProject" --run "TestingSuite2" --spawn 9 --case-ok --section "CARBON LIQUEFACTION" --mock t/test_subtest.tap});
+$out = `@args`;
+is($? >> 8, 0, "Exit code OK reported with spawn");
+$matches = () = $out =~ m/with specified sections/ig;
+is($matches,1,"Attempts to spawn work");
+
 #Test that help works
 @args = ($^X,qw{bin/testrail-report --help});
 $out = `@args`;