#!/usr/bin/perl -wT # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Test Runner System. # # The Initial Developer of the Original Code is Maciej Maczynski. # Portions created by Maciej Maczynski are Copyright (C) 2001 # Maciej Maczynski. All Rights Reserved. # # Contributor(s): Greg Hendricks use strict; use lib "."; use Bugzilla; use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Util; use Bugzilla::User; use Bugzilla::Version; use Bugzilla::Testopia::Util; use Bugzilla::Testopia::TestRun; use Bugzilla::Testopia::TestCaseRun; use Bugzilla::Testopia::TestTag; use Bugzilla::Testopia::Environment; use Bugzilla::Testopia::Search; use Bugzilla::Testopia::Table; use Bugzilla::Testopia::Product; use vars qw($vars); my $template = Bugzilla->template; my $query_limit = 15000; require "globals.pl"; Bugzilla->login(LOGIN_REQUIRED); my $dbh = Bugzilla->dbh; my $cgi = Bugzilla->cgi; my $run_id = trim($cgi->param('run_id') || ''); unless ($run_id){ print $cgi->header; $template->process("testopia/run/choose.html.tmpl", $vars) || ThrowTemplateError($template->error()); exit; } validate_test_id($run_id, 'run'); push @{$::vars->{'style_urls'}}, 'testopia/css/default.css'; my $serverpush = support_server_push($cgi); my $action = $cgi->param('action') || ''; #################### ### Edit Actions ### #################### if ($action eq 'Commit'){ print $cgi->header; my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError("testopia-read-only", {'object' => $run}) unless $run->canedit; do_update($run); $vars->{'tr_message'} = "Test run updated"; $vars->{'backlink'} = $run; display($run); } elsif ($action eq 'History'){ print $cgi->header; my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError("testopia-permission-denied", {'object' => $run}) unless $run->canview; $vars->{'run'} = $run; $template->process("testopia/run/history.html.tmpl", $vars) || ThrowTemplateError($template->error()); } ############# ### Clone ### ############# elsif ($action =~ /^Clone/){ print $cgi->header; my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError("testopia-read-only", {'object' => $run->plan}) unless $run->plan->canedit; my $case_list = $cgi->param('case_list'); do_update($run); my @ids; foreach my $id (split(",", $case_list)){ detaint_natural($id); push @ids, $id; } my $dbh = Bugzilla->dbh; my $ref; if ($case_list){ $ref = $dbh->selectcol_arrayref( "SELECT DISTINCT case_id FROM test_case_runs WHERE case_run_id IN (" . join(",",@ids) . ")"); } $vars->{'product'} = Bugzilla::Testopia::Product->new($run->plan->product_id); $vars->{'run'} = $run; $vars->{'case_list'} = join(",", @$ref) if ($action =~/These Cases/ && $ref); $vars->{'caserun'} = Bugzilla::Testopia::TestCaseRun->new({'case_run_id' => 0}); $template->process("testopia/run/clone.html.tmpl", $vars) || ThrowTemplateError($template->error()); } elsif ($action eq 'do_clone'){ my $run = Bugzilla::Testopia::TestRun->new($run_id); if ($serverpush) { print $cgi->multipart_init(); print $cgi->multipart_start(); $template->process("list/server-push.html.tmpl", $vars) || ThrowTemplateError($template->error()); } ThrowUserError("testopia-read-only", {'object' => $run->plan}) unless $run->plan->canedit; my $summary = $cgi->param('summary'); my $build = $cgi->param('build'); my $plan_id = $cgi->param('plan_id'); trick_taint($summary); detaint_natural($build); validate_test_id($plan_id, 'plan'); my $manager = $cgi->param('keepauthor') ? $run->manager->id : Bugzilla->user->id; my $newrun = Bugzilla::Testopia::TestRun->new($run->clone($summary, $manager, $plan_id, $build)); if($cgi->param('copy_tags')){ foreach my $tag (@{$run->tags}){ my $newtag = Bugzilla::Testopia::TestTag->new({ tag_name => $tag->name }); # Store will return the id of the existing tag if it exists # or create it if it does not my $newtagid = $newtag->store; $newrun->add_tag($newtagid); } } my $progress_interval = 250; if ($cgi->param('case_list')){ my @case_ids; foreach my $id (split(",", $cgi->param('case_list'))){ detaint_natural($id); my $case = Bugzilla::Testopia::TestCase->new($id); unless ($case->canview){ print $cgi->multipart_end if $serverpush; ThrowUserError('testopia-permission-denied', {'object' => $case}); } push @case_ids, $id } my $i = 0; my $total = scalar @case_ids; foreach my $id (@case_ids){ $i++; if ($i % $progress_interval == 0 && $serverpush){ print $cgi->multipart_end; print $cgi->multipart_start; $vars->{'complete'} = $i; $vars->{'total'} = $total; $template->process("testopia/progress.html.tmpl", $vars) || ThrowTemplateError($template->error()); } $newrun->add_case_run($id); } } if ($cgi->param('copy_test_cases')){ if ($cgi->param('status')){ my @status = $cgi->param('status'); foreach my $s (@status){ detaint_natural($s); } my $ref = $dbh->selectcol_arrayref( "SELECT case_id FROM test_case_runs WHERE run_id = ? AND case_run_status_id IN (". join(",", @status) .") AND iscurrent = 1", undef, $run->id); my $i = 0; my $total = scalar @$ref; foreach my $case_id (@{$ref}){ $i++; if ($i % $progress_interval == 0 && $serverpush){ print $cgi->multipart_end; print $cgi->multipart_start; $vars->{'complete'} = $i; $vars->{'total'} = $total; $template->process("testopia/progress.html.tmpl", $vars) || ThrowTemplateError($template->error()); } $newrun->add_case_run($case_id); } } else { my $i = 0; my $total = scalar @{$run->cases}; foreach my $case (@{$run->cases}){ $i++; if ($i % $progress_interval == 0 && $serverpush){ print $cgi->multipart_end; print $cgi->multipart_start; $vars->{'complete'} = $i; $vars->{'total'} = $total; $template->process("testopia/progress.html.tmpl", $vars) || ThrowTemplateError($template->error()); } $newrun->add_case_run($case->id); } } } if ($serverpush) { print $cgi->multipart_end; print $cgi->multipart_start; } else { print $cgi->header; } $cgi->delete_all; $cgi->param('run_id', $newrun->id); $vars->{'tr_message'} = "Test run cloned"; $vars->{'backlink'} = $run; display($newrun); print $cgi->multipart_final if $serverpush; } elsif ($action eq 'Filter'){ $cgi->send_cookie(-name => 'TESTOPIA-FILTER-RUN-' . $run_id, -value => $cgi->canonicalise_query('run_id'), -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'); $vars->{'filtered'} = 1; print $cgi->header; display(Bugzilla::Testopia::TestRun->new($run_id)); } elsif ($action eq 'clear_filter'){ $cgi->remove_cookie('TESTOPIA-FILTER-RUN-' . $run_id); $vars->{'filtered'} = 0; print $cgi->header; display(Bugzilla::Testopia::TestRun->new($run_id)); } #################### ### Ajax Actions ### #################### elsif ($action eq 'addcc'){ my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError("testopia-read-only", {'object' => $run}) unless $run->canedit; my @cclist = split(/[\s,]+/, $cgi->param('cc')); my %ccids; foreach my $email (@cclist){ my $ccid = login_to_id($email) || ThrowUserError("invalid_username", { name => $email }); if ($ccid && !$ccids{$ccid}) { $ccids{$ccid} = 1; } } foreach my $ccid (keys(%ccids)) { $run->add_cc($ccid); } my $cc = get_cc_xml($run); print $cgi->header; print $cc; } elsif ($action eq 'removecc'){ my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError('insufficient-case-perms') unless $run->canedit; foreach my $ccid (split(",", $cgi->param('cc'))){ detaint_natural($ccid); $run->remove_cc($ccid); } my $cc = get_cc_xml($run); print $cgi->header; print $cc; } elsif ($action eq 'Delete'){ print $cgi->header; my $run = Bugzilla::Testopia::TestRun->new($run_id); ThrowUserError("testopia-no-delete", {'object' => $run}) unless $run->candelete; $vars->{'run'} = $run; $template->process("testopia/run/delete.html.tmpl", $vars) || ThrowTemplateError($template->error()); } elsif ($action eq 'do_delete'){ my $run = Bugzilla::Testopia::TestRun->new($run_id); unless ($run->candelete){ print $cgi->header; ThrowUserError("testopia-no-delete", {'object' => $run}); } if ($serverpush) { print $cgi->multipart_init(); print $cgi->multipart_start(); $vars->{'complete'} = 1; $vars->{'total'} = 250; $template->process("testopia/progress.html.tmpl", $vars) || ThrowTemplateError($template->error()); $run->obliterate($cgi,$template); } else{ $run->obliterate; } if ($serverpush) { print $cgi->multipart_end; print $cgi->multipart_start; } else { print $cgi->header; } $vars->{'deleted'} = 1; $template->process("testopia/run/delete.html.tmpl", $vars) || ThrowTemplateError($template->error()); print $cgi->multipart_final if $serverpush; } #################### ### Just show it ### #################### else { print $cgi->header; display(Bugzilla::Testopia::TestRun->new($run_id)); } ################### ### Helper Subs ### ################### #TODO: Replace this with json sub get_cc_xml { my ($run) = @_; my $ret = ""; foreach my $c (@{$run->cc}){ $ret .= ""; $ret .= "". $c->{'id'} .""; $ret .= "". $c->{'login'} .""; $ret .= ""; } $ret .= ""; return $ret; } sub do_update { my ($run) = @_; my $timestamp; $timestamp = $run->stop_date; $timestamp = undef if $cgi->param('status') && $run->stop_date; $timestamp = get_time_stamp() if !$cgi->param('status') && !$run->stop_date; my $summary = $cgi->param('summary') || ''; my $prodver = Bugzilla::Version::check_version($run->plan->product, $cgi->param('product_version')); my $planver = $cgi->param('plan_version'); my $build = $cgi->param('build'); my $env = $cgi->param('environment'); my $manager = login_to_id(trim($cgi->param('manager'))) ||ThrowUserError("invalid_username", { name => $cgi->param('manager') }); my $notes = trim($cgi->param('notes')); ThrowUserError('testopia-missing-required-field', {'field' => 'summary'}) if ($cgi->param('summary') eq ''); ThrowUserError('testopia-missing-required-field', {'field' => 'environment'}) if ($env eq ''); trick_taint($summary); trick_taint($planver); trick_taint($notes); detaint_natural($build); detaint_natural($env); validate_test_id($build, 'build'); validate_test_id($env, 'environment'); #TODO: Are notes something we want in the history? #$run->update_notes($notes); my %newvalues = ( 'summary' => $summary, 'product_version' => $prodver->name, 'plan_text_version' => $planver, 'build_id' => $build, 'environment_id' => $env, 'stop_date' => $timestamp, 'manager_id' => $manager, 'notes' => $notes ); $run->update(\%newvalues); # Add new tags foreach my $tag_name (split(/[,]+/, $cgi->param('newtag'))){ trick_taint($tag_name); my $tag = Bugzilla::Testopia::TestTag->new({tag_name => $tag_name}); my $tag_id = $tag->store; $run->add_tag($tag_id); } $cgi->delete_all; $cgi->param('run_id', $run->id); } sub display { my $run = shift; ThrowUserError("testopia-permission-denied", {'object' => $run}) unless $run->canview; # See if there is a saved filter if ($cgi->cookie('TESTOPIA-FILTER-RUN-' . $run_id) && $action ne 'Filter' && $action ne 'clear_filter'){ $cgi = Bugzilla::CGI->new($cgi->cookie('TESTOPIA-FILTER-RUN-' . $run_id)); $cgi->param('run_id', $run_id); $vars->{'filtered'} = 1; } $cgi->param('current_tab', 'case_run'); my $search = Bugzilla::Testopia::Search->new($cgi); my $table = Bugzilla::Testopia::Table->new('case_run', 'tr_show_run.cgi', $cgi, undef, $search->query); ThrowUserError('testopia-query-too-large', {'limit' => $query_limit}) if $table->view_count > $query_limit; my $case = Bugzilla::Testopia::TestCase->new({'case_id' => 0}); $vars->{'fullwidth'} = 1; $vars->{'expand_report'} = $cgi->param('expand_report') || 0; $vars->{'expand_filter'} = $cgi->param('expand_filter') || 0; $vars->{'caserun'} = Bugzilla::Testopia::TestCaseRun->new({}); $vars->{'case'} = Bugzilla::Testopia::TestCase->new({}); $vars->{'run'} = $run; $vars->{'table'} = $table; $vars->{'action'} = 'Commit'; if ($cgi->param('case_id')){ $vars->{'expand_filter'} = 1; $vars->{'filtered'} = 1; } $template->process("testopia/run/show.html.tmpl", $vars) || ThrowTemplateError($template->error()); }