#! /usr/bin/env perl # ABSTRACT: Upload your TAP results to TestRail after they've finished # PODNAME: testrail-report =head1 SYNOPSIS testrail-report [OPTIONS] tapfile prove -v sometest.t > results.tap && testrail-report [OPTIONS] results.tap prove -v sometest.t | testrail-report [OPTIONS] prove -PTestRail='http://some.testlink.install/,someUser,somePassword,someProject,someRun,0,step_results' sometest.t =head1 DESCRIPTION testrail-report - report raw TAP results to a TestRail install USAGE: =head2 PARAMETERS: =head3 MANDATORY PARAMETERS --project [someproject] : associate results (if any) with theprovided project name. --run [somerun] : associates results (if any) with the provided run name. IF none of these options are provided, you will be asked to type these in as needed, supposing you are not redirecting input (such as piping into this command). =head3 CONFIG OVERRIDES In your \$HOME, put a file called .testrailrc with key=value syntax separated by newlines. Valid Keys are: apiurl,user,password =head3 CONFIG OPTIONS 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. =head3 BEHAVIOR --case-ok : Whether to consider each OK to correspond to a test in TestRail --step-results [name] : 'System Name' of a 'step_results' type field to set for your tests. These options are mutually exclusive. If neither is set, the overall result of the test will be used as the pass/fail for the test. =head2 PROVE PLUGIN: passing -PTestRail=apiurl,user,pass,project,run to prove will automatically upload your test results while the test is running if real-time results are desired. See L for more information. =head2 REQUIREMENTS: Your TestRail install must have 3 custom statuses with the internal names 'skip', 'todo_pass', and 'todo_fail', to represent those states which TAP can have. =head2 TESTING OPTIONS: --mock don't do any real HTTP requests. =cut use strict; use warnings; use Getopt::Long; use Term::ANSIColor qw(colorstrip); use Test::Rail::Parser; use IO::Interactive::Tiny (); print "testrail-report\n----------------------\n"; sub help { print "testrail-report - report raw TAP results to a TestRail install USAGE: testrail-report [OPTIONS] tapfile prove -v sometest.t > results.tap && testrail-report [OPTIONS] \\ results.tap prove -v sometest.t | testrail-report [OPTIONS] prove -PTestRail='http://some.testlink.install/','someUser',\\ 'somePassword' sometest.t PARAMETERS: [MANDATORY PARAMETERS] --project [someproject] : associate results (if any) with the provided project name. --run [somerun] : associates results (if any) with the provided run name. IF none of these options are provided, you will be asked to type these in as needed, supposing you are not redirecting input (such as piping into this command). [CONFIG OVERRIDES] In your \$HOME, put a file called .testrailrc with key=value syntax separated by newlines. Valid Keys are: apiurl,user,password [CONFIG OPTIONS] - 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. [BEHAVIOR] --case-ok : Whether to consider each OK to correspond to a test in TestRail --step-results [name] : 'System Name' of a 'step_results' type field to set for your tests. These options are mutually exclusive. If neither is set, the overall result of the test will be used as the pass/fail for the test. PROVE PLUGIN: passing -PTestRail=apiurl,user,pass,project,run to prove will automatically upload your test results while the test is running if real-time results are desired. See App::Prove::Plugin::TestRail for more information. REQUIREMENTS: Your TestRail install must have 3 custom statuses with the internal names 'skip', 'todo_pass', and 'todo_fail', to represent those states which TAP can have. TESTING OPTIONS: --mock don't do any real HTTP requests. "; exit 0; } sub userInput { $| = 1; my $rt = ; chomp $rt; return $rt; } sub parseConfig { my $results = {}; my $arr =[]; open(my $fh, '<', $ENV{"HOME"} . '/.testrailrc') or return (undef,undef,undef);#couldn't open! while (<$fh>) { chomp; @$arr = split(/=/,$_); if (scalar(@$arr) != 2) { warn("Could not parse $_ in tlreport config\n"); next; } $results->{lc($arr->[0])} = $arr->[1]; } close($fh); return ($results->{'apiurl'},$results->{'password'},$results->{'user'}); } #Main loop------------ my ($help,$apiurl,$user,$password,$project,$run,$case_per_ok,$step_results,$mock); #parse switches GetOptions( 'run=s' => \$run, 'apiurl=s' => \$apiurl, 'password=s' => \$password, 'user=s' => \$user, 'project=s' => \$project, 'case-ok' => \$case_per_ok, 'step-results=s' => \$step_results, 'mock' => \$mock, 'help' => \$help ); if ($help) { help(); } #Parse config file if we are missing api url/key or user if (-e $ENV{"HOME"} . '/.testrailrc' && (!$apiurl || !$password || !$user) ) { ($apiurl,$password,$user) = parseConfig(); } #XXX not even close to optimized, don't slurp in the future #If argument is passed use it instead of stdin my $file = $ARGV[0]; die "No Such File $file" if ($file && !-e $file); my ($fh,$fcontents,@files); if ($file) { open($fh,'<',$file); while (<$fh>) { $_ = colorstrip($_); #strip prove brain damage s/^\s*//g; #Fix more brain damage if ($_ =~ /(.*)\s\.\.$/) { #File marker from default prove unless ($_ =~ /^[ok|not ok] - /) { push(@files,$fcontents) if $fcontents; $fcontents = ''; } } $fcontents .= $_; } close($fh); push(@files,$fcontents) if $fcontents; } else { #Just read STDIN, print help if no file was passed if (IO::Interactive::Tiny::is_interactive()) { print "ERROR: no file passed, and no data piped in! See --help for usage.\n"; exit(1); } if ( !$run || !$apiurl || !$password || !$user || !$project ) { print "ERROR: Interactive mode not allowed when piping input. See --help for options.\n"; exit(1); } while (<>) { $_ = colorstrip($_); #strip prove brain damage s/^\s*//g; #Fix prove brain damage if ($_ =~ /(.*)\s\.\.$/) { #File marker from default prove unless ($_ =~ /^[ok|not ok] - /) { push(@files,$fcontents) if $fcontents; $fcontents = ''; } } $fcontents .= $_; } help() if !$fcontents; #Nothing passed to stdin! push(@files,$fcontents) if $fcontents; } #Interrogate user if they didn't provide info if (!$apiurl) { print "Type the API endpoint url for your testLink install below:\n"; $apiurl = userInput(); } if (!$user) { print "Type your testLink user name below:\n"; $user = userInput(); } if (!$password) { print "Type the password for your testLink user below:\n"; $password = userInput(); } if (!$apiurl || !$password || !$user) { print "ERROR: api url, username and password cannot be blank.\n"; exit 1; } #Interrogate user if they didn't provide info if (!$project) { print "Type the name of the project you are testing under:\n"; $project = userInput(); } # Interrogate user if options were not passed if (!$run) { print "Type the name of the existing run you would like to run against:\n"; $run = userInput(); } my $debug = 0; my $browser; if ($mock) { use Test::LWP::UserAgent::TestRailMock; $browser = $Test::LWP::UserAgent::TestRailMock::mockObject; $debug = 1; } my $tap; foreach my $phil (@files) { print "Processing $phil...\n"; $tap = Test::Rail::Parser->new({ 'tap' => $phil, 'apiurl' => $apiurl, 'user' => $user, 'pass' => $password, 'run' => $run, 'project' => $project, 'case_per_ok' => $case_per_ok, 'step_results' => $step_results, 'debug' => $debug, 'browser' => $browser, 'merge' => 1 }); $tap->run(); } print "Done.\n"; #all done 0; __END__ =head1 SEE ALSO L L L =head1 SPECIAL THANKS Thanks to cPanel Inc, for graciously funding the creation of this module.