bugzilla-4intranet/Bugzilla/Testopia/TestCaseRun.pm

1194 lines
33 KiB
Perl

# -*- 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 Testopia System.
#
# The Initial Developer of the Original Code is Greg Hendricks.
# Portions created by Greg Hendricks are Copyright (C) 2006
# Novell. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
package Bugzilla::Testopia::TestCaseRun;
use strict;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Bug;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Environment;
use Bugzilla::Testopia::Build;
use Bugzilla::Testopia::Constants;
use Bugzilla::Testopia::Attachment;
use Date::Format;
use Date::Parse;
use base qw(Exporter Bugzilla::Object);
#@Bugzilla::Testopia::TestCaseRun::EXPORT = qw(lookup_status lookup_status_by_name);
###############################
#### Initialization ####
###############################
=head1 FIELDS
case_run_id
run_id
case_id
assignee
testedby
case_run_status_id
case_text_version
build_id
environment_id
notes
running_date
close_date
iscurrent
sortkey
=cut
use constant DB_TABLE => "test_case_runs";
use constant ID_FIELD => "case_run_id";
use constant NAME_FIELD => "";
use constant DB_COLUMNS => qw(
case_run_id
run_id
case_id
assignee
testedby
case_run_status_id
case_text_version
build_id
environment_id
notes
running_date
close_date
iscurrent
sortkey
);
use constant REQUIRED_CREATE_FIELDS => qw(case_id run_id build_id environment_id case_run_status_id);
use constant UPDATE_COLUMNS => qw(case_run_status_id case_text_version notes sortkey);
use constant VALIDATORS => {
case_id => \&_check_case_id,
build_id => \&_check_build_id,
run_id => \&_check_run_id,
environment_id => \&_check_env_id,
case_text_version => \&_check_case_text_version,
case_run_status_id => \&_check_case_run_status_id,
};
sub report_columns {
my $self = shift;
my %columns;
# Changes here need to match Report.pm
$columns{'Build'} = "build";
$columns{'Status'} = "case_run_status";
$columns{'Environment'} = "environment";
$columns{'Assignee'} = "assignee";
$columns{'Tested By'} = "testedby";
$columns{'Milestone'} = "milestone";
$columns{'Case Tags'} = "case_tags";
$columns{'Run Tags'} = "run_tags";
$columns{'Requirement'} = "requirement";
$columns{'Priority'} = "priority";
$columns{'Default tester'} = "default_tester";
$columns{'Category'} = "category";
$columns{'Component'} = "component";
my @result;
push @result, {'name' => $_, 'id' => $columns{$_}} foreach (sort(keys %columns));
unshift @result, {'name' => '<none>', 'id'=> ''};
return \@result;
}
###############################
#### Validators ####
###############################
sub _check_case_id {
my ($invocant, $id) = @_;
return Bugzilla::Testopia::Util::validate_test_id($id, 'case');
}
sub _check_run_id {
my ($invocant, $id) = @_;
return Bugzilla::Testopia::Util::validate_test_id($id, 'run');
}
sub _check_build_id {
my ($invocant, $id) = @_;
return Bugzilla::Testopia::Util::validate_test_id($id, 'build');
}
sub _check_env_id {
my ($invocant, $id) = @_;
return Bugzilla::Testopia::Util::validate_test_id($id, 'environment');
}
sub _check_case_run_status_id {
my ($invocant, $status) = @_;
$status = trim($status);
my $status_id;
if ($status =~ /^\d+$/){
$status_id = Bugzilla::Testopia::Util::validate_selection($status, 'case_run_status_id', 'test_case_run_status');
}
else {
$status_id = lookup_status_by_name($status);
}
ThrowUserError('invalid_status') unless $status_id;
return $status_id;
}
sub _check_case_text_version {
my ($invocant, $version) = @_;
return $version =~ /\d+/ ? $version : 1;
}
sub _check_assignee{
my ($invocant, $tester) = @_;
$tester = trim($tester);
return unless $tester;
if ($tester =~ /^\d+$/){
$tester = Bugzilla::User->new($tester);
return $tester->id;
}
else {
my $id = login_to_id($tester, THROW_ERROR);
return $id;
}
}
###############################
#### Mutators ####
###############################
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my ($param, $case_id, $build_id, $env_id) = (@_);
my $dbh = Bugzilla->dbh;
# We want to be able to supply an empty object to the templates for numerous
# lists etc. This is much cleaner than exporting a bunch of subroutines and
# adding them to $vars one by one. Probably just Laziness shining through.
if (ref $param eq 'HASH'){
if (!keys %$param || $param->{PREVALIDATED}){
bless($param, $class);
return $param;
}
}
elsif ($case_id && detaint_natural($case_id)
&& $build_id && detaint_natural($build_id)
&& $env_id && detaint_natural($env_id)){
my $run_id = $param;
detaint_natural($case_id) || return undef;
($param) = $dbh->selectrow_array(
"SELECT case_run_id FROM test_case_runs
WHERE case_id = ?
AND run_id = ?
AND build_id = ?
AND environment_id = ?",
undef, ($case_id, $run_id, $build_id, $env_id));
}
unshift @_, $param;
my $self = $class->SUPER::new(@_);
return $self;
}
sub create {
my ($class, $params) = @_;
$class->SUPER::check_required_create_fields($params);
my $field_values = $class->run_create_validators($params);
$field_values->{iscurrent} = 1;
my $self = $class->SUPER::insert_create_data($field_values);
$self->set_as_current;
return $self;
}
###############################
#### Methods ####
###############################
=head2 check_exists
Checks for an existing entry with the same build and environment for this
case and run and switches self to that object.
=cut
sub switch {
my $self = shift;
my ($build_id, $env_id ,$run_id, $case_id) = @_;
detaint_natural($build_id);
detaint_natural($env_id);
detaint_natural($run_id);
detaint_natural($case_id);
$run_id ||= $self->{'run_id'};
$case_id ||= $self->{'case_id'};
$build_id ||= $self->{'build_id'};
$env_id ||= $self->{'environment_id'};
my $dbh = Bugzilla->dbh;
my ($is) = $dbh->selectrow_array(
"SELECT case_run_id
FROM test_case_runs
WHERE run_id = ?
AND case_id = ?
AND build_id = ?
AND environment_id = ?",
undef, ($run_id, $case_id, $build_id, $env_id));
if ($is){
$self = Bugzilla::Testopia::TestCaseRun->new($is);
}
else {
my $oldbuild = $self->{'build_id'};
my $oldenv = $self->{'environment_id'};
$self = $self->create({
'run_id' => $self->{'run_id'},
'case_id' => $self->{'case_id'},
'assignee' => $self->{'assignee'},
'case_text_version' => $self->{'case_text_version'},
'build_id' => $build_id,
'environment_id' => $env_id,
'case_run_status_id' => IDLE,
});
if ($oldbuild != $build_id){
my $build = Bugzilla::Testopia::Build->new($oldbuild);
my $note = "Build Changed by ". Bugzilla->user->login;
$note .= ". Old build: '". $build->name;
$note .= "' New build: '". $self->build->name;
$note .= "'. Resetting to IDLE.";
$self->append_note($note);
}
if ($oldenv != $env_id){
my $environment = Bugzilla::Testopia::Environment->new($oldenv);
my $note = "Environment Changed by ". Bugzilla->user->login;
$note .= ". Old environment: '". $environment->name;
$note .= "' New environment: '". $self->environment->name;
$note .= "'. Resetting to IDLE.";
$self->append_note($note);
}
$self->set_as_current;
}
return $self;
}
sub TO_JSON {
my $self = shift;
my $obj;
my $json = new JSON;
foreach my $field ($self->DB_COLUMNS){
$obj->{$field} = $self->{$field};
}
my @bugs;
foreach my $b (@{$self->bugs}){
push @bugs, { bug_id => $b->bug_id, closed => Bugzilla::Bug::is_open_state($b->{'bug_status'}) ? JSON::false : JSON::true};
}
my $bugs = { bugs => \@bugs };
$obj->{'assignee_name'} = $self->assignee->login if $self->assignee;
$obj->{'requirement'} = $self->case->requirement if $self->case;
$obj->{'testedby'} = $self->testedby->login if $self->testedby;
$obj->{'status'} = $self->status;
$obj->{'build_name'} = $self->build->name if $self->build;
$obj->{'env_name'} = $self->environment->name if $self->environment;
$obj->{'env_id'} = $self->environment->id if $self->environment;
$obj->{'category'} = $self->case->category->name if $self->case && $self->case->category;
$obj->{'priority'} = $self->case->priority if $self->case;
$obj->{'bug_count'} = $self->bug_count;
$obj->{'case_summary'} = $self->case->summary if $self->case;
$obj->{'component'} = @{$self->case->components}[0]->name if ($self->case && scalar @{$self->case->components});
$obj->{'type'} = $self->type;
$obj->{'id'} = $self->id;
$obj->{'sortkey'} = $self->sortkey;
$obj->{'bug_list'} = $bugs;
return $json->encode($obj);
}
=head2 _update_fields
Update this case-run in the database if a change is made to an
updatable field.
=cut
sub _update_fields{
my $self = shift;
my ($newvalues) = @_;
my $dbh = Bugzilla->dbh;
if ($newvalues->{'case_run_status_id'} && $newvalues->{'case_run_status_id'} == FAILED){
$self->_update_deps(BLOCKED);
}
elsif ($newvalues->{'case_run_status_id'} && $newvalues->{'case_run_status_id'} == PASSED){
$self->_update_deps(IDLE);
}
$dbh->bz_start_transaction();
foreach my $field (keys %{$newvalues}){
$dbh->do("UPDATE test_case_runs
SET $field = ? WHERE case_run_id = ?",
undef, $newvalues->{$field}, $self->{'case_run_id'});
$self->{$field} = $newvalues->{$field};
}
$dbh->bz_commit_transaction();
return $self->{'case_run_id'};
}
=head2 set_as_current
Sets this case-run as the current or active one in the history
list of case-runs of this build and case_id
=cut
sub set_as_current {
my $self = shift;
my ($caserun) = @_;
$caserun = $self->{'case_run_id'} unless defined $caserun;
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
$dbh->do("UPDATE test_case_runs
SET iscurrent = 0
WHERE case_id = ? AND run_id = ?",
undef, ($self->case_id, $self->run_id));
$dbh->do("UPDATE test_case_runs
SET iscurrent = 1
WHERE case_run_id = ?",
undef, $caserun);
$dbh->bz_commit_transaction();
}
=head2 set_status
Sets the status on a case-run and updates the close_date and testedby
if the status is a closed status.
=cut
sub set_status {
my $self = shift;
my ($status_id, $update_bugs) = @_;
$status_id = $self->_check_case_run_status_id($status_id);
return if $self->status_id == $status_id;
my $oldstatus = $self->status;
my $newstatus = lookup_status($status_id);
$self->_update_fields({'case_run_status_id' => $status_id});
if ($status_id == IDLE){
$self->_update_fields({'close_date' => undef});
$self->_update_fields({'testedby' => undef});
$self->{'close_date'} = undef;
$self->{'testedby'} = undef;
}
elsif ($status_id == RUNNING || $status_id == PAUSED){
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
$self->_update_fields({'running_date' => $timestamp}) if $status_id == RUNNING;
$self->_update_fields({'close_date' => undef});
$self->{'close_date'} = undef;
}
else {
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
$self->_update_fields({'close_date' => $timestamp});
$self->_update_fields({'testedby' => Bugzilla->user->id});
$self->{'close_date'} = $timestamp;
$self->{'testedby'} = Bugzilla->user->id;
$self->update_bugs('REOPENED') if ($status_id == FAILED && $update_bugs);
$self->update_bugs('VERIFIED') if ($status_id == PASSED && $update_bugs);
}
$self->set_as_current;
my $note = "Status changed from $oldstatus to $newstatus by ". Bugzilla->user->login;
$note .= " for build '". $self->build->name ."' and environment '". $self->environment->name;
$self->append_note($note);
$self->{'case_run_status_id'} = $status_id;
delete $self->{'status'};
}
sub set_sortkey {
my $self = shift;
my ($sortkey) = @_;
my $dbh = Bugzilla->dbh;
detaint_natural($sortkey);
$dbh->do("UPDATE test_case_runs SET sortkey = ?
WHERE case_id = ? AND run_id = ?",
undef, ($sortkey, $self->case_id, $self->run_id));
$self->{'sortkey'} = $sortkey;
}
=head2 set_assignee
Sets the assigned tester for the case-run
=cut
sub set_assignee {
my $self = shift;
my ($user_id) = @_;
my $assignee = $self->_check_assignee($user_id);
my $oldassignee = $self->assignee->login;
my $newassignee = Bugzilla::User->new($assignee);
$self->_update_fields({'assignee' => $assignee});
$self->{'assignee'} = $assignee;
my $note = "Assignee changed from $oldassignee to ". $newassignee->login;
$note .= " by ". Bugzilla->user->login;
$note .= " for build '". $self->build->name;
$note .= "' and environment '". $self->environment->name;
$self->append_note($note);
}
=head2 append_note
Updates the notes field for the case-run
=cut
sub append_note {
my $self = shift;
my ($note) = @_;
return unless $note;
my $timestamp = time2str("%c", time());
$note = "$timestamp: $note";
if ($self->{'notes'}){
$note = $self->{'notes'} . "\n" . $note;
}
$self->_update_fields({'notes' => $note});
$self->{'notes'} = $note;
}
=head2 _update_deps
Private method for updating blocked test cases. If the pre-requisite
case fails, the blocked test cases in a run get a status of BLOCKED
if it passes they are set back to IDLE. This only happens to the
current case run and only if it doesn't already have a closed status.
=cut
sub _update_deps {
my $self = shift;
my ($status) = @_;
my $deplist = $self->case->get_dep_tree;
return unless $deplist;
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
my $caseruns = $dbh->selectcol_arrayref(
"SELECT case_run_id
FROM test_case_runs
WHERE iscurrent = 1
AND run_id = ?
AND case_run_status_id IN(". join(',', (IDLE,RUNNING,PAUSED,BLOCKED)) .")
AND case_id IN (". join(',', @$deplist) .")",
undef, $self->{'run_id'});
my $sth = $dbh->prepare_cached(
"UPDATE test_case_runs
SET case_run_status_id = ?
WHERE case_run_id = ?");
foreach my $id (@$caseruns){
$sth->execute($status, $id);
}
$dbh->bz_commit_transaction();
$self->{'updated_deps'} = $caseruns;
}
=head2 get_case_run_list
Returns a reference to a list of case-runs for the given case and run
=cut
sub get_case_run_list {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref(
"SELECT case_run_id FROM test_case_runs
WHERE case_id = ? AND run_id = ?", undef,
($self->{'case_id'}, $self->{'run_id'}));
return $ref;
}
sub get_history {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectall_arrayref(
"SELECT tcr.case_run_id, tcr.close_date, tcr.iscurrent, tb.name AS build_name,
te.name as env_name, p.login_name AS testedby, tcrs.name AS status_name
FROM test_case_runs tcr
INNER JOIN test_builds tb ON tcr.build_id = tb.build_id
INNER JOIN test_environments te ON tcr.environment_id = te.environment_id
INNER JOIN test_case_run_status tcrs ON tcr.case_run_status_id = tcrs.case_run_status_id
LEFT JOIN profiles p ON tcr.testedby = p.userid
WHERE case_id = ? AND run_id = ?", {'Slice' =>{}},
($self->{'case_id'}, $self->{'run_id'}));
return $ref;
}
=head2 get_status_list
Returns a list reference of the legal statuses for a test case-run
=cut
sub get_status_list {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectall_arrayref(
"SELECT case_run_status_id AS id, name
FROM test_case_run_status
ORDER BY sortkey", {'Slice' =>{}});
return $ref
}
=head2 attach_bug
Attaches the specified bug to this test case-run
=cut
sub attach_bug {
my $self = shift;
my ($bugs, $caserun_id) = @_;
$bugs = Bugzilla::Testopia::TestCase->_check_bugs($bugs, "ATTACH");
$caserun_id ||= $self->{'case_run_id'};
my $dbh = Bugzilla->dbh;
$dbh->bz_start_transaction();
foreach my $bug (@$bugs){
trick_taint($bug) if $bug;
trick_taint($caserun_id) if $caserun_id;
my ($exists) = $dbh->selectrow_array(
"SELECT bug_id
FROM test_case_bugs
WHERE case_run_id=?
AND bug_id=?",
undef, ($caserun_id, $bug));
if ($exists) {
$dbh->bz_commit_transaction();
return;
}
my ($check) = $dbh->selectrow_array(
"SELECT bug_id
FROM test_case_bugs
WHERE case_id=?
AND bug_id=?
AND case_run_id=?",
undef, ($caserun_id, $bug, undef));
if ($check){
$dbh->do("UPDATE test_case_bugs
SET test_case_run_id = ?
WHERE case_id = ?
AND bug_id = ?",
undef, ($bug, $self->{'case_run_id'}));
}
else{
$dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id, case_id)
VALUES(?,?,?)", undef, ($bug, $self->{'case_run_id'}, $self->{'case_id'}));
}
}
$dbh->bz_commit_transaction();
}
=head2 detach_bug
Removes the association of the specified bug from this test case-run
=cut
sub detach_bug {
my $self = shift;
my ($bug) = @_;
my $dbh = Bugzilla->dbh;
trick_taint($bug) if $bug;
$dbh->do("DELETE FROM test_case_bugs
WHERE bug_id = ?
AND case_run_id = ?",
undef, ($bug, $self->{'case_run_id'}));
}
=head2 update_bugs
Updates bug status depending on whether the case passed or failed. If
the case failed it will reopen any attached bugs that are closed. If it
passed it will mark RESOLVED bugs VERIFIED.
=cut
sub update_bugs {
my $self = shift;
my ($status) = @_;
my $resolution;
my $dbh = Bugzilla->dbh;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
foreach my $bug (@{$self->bugs}){
my $oldstatus = $bug->bug_status;
my $oldresolution = $bug->resolution;
next if ($status eq 'VERIFIED' && $oldstatus ne 'RESOLVED');
next if ($status eq 'REOPENED' && $oldstatus !~ /(RESOLVED|VERIFIED|CLOSED)/);
next if $oldresolution eq 'DUPLICATE';
if ($status eq 'REOPENED'){
$resolution = '';
}
else{
$resolution = $oldresolution;
}
my $comment = "Status updated by Testopia: ". Bugzilla->params->{"urlbase"};
$comment .= "tr_show_case.cgi?case_id=" . $self->case->id;
$dbh->bz_start_transaction();
$dbh->do("UPDATE bugs
SET bug_status = ?,
resolution = ?,
delta_ts = ?
WHERE bug_id = ?",
undef,($status, $resolution, $timestamp, $bug->bug_id));
LogActivityEntry($bug->bug_id, 'bug_status', $oldstatus,
$status, Bugzilla->user->id, $timestamp);
LogActivityEntry($bug->bug_id, 'resolution', $bug->resolution, '',
Bugzilla->user->id, $timestamp) if ($status eq 'REOPENED');
AppendComment($bug->bug_id, Bugzilla->user->id, $comment,
!Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'}), $timestamp);
$dbh->bz_commit_transaction();
}
}
=head2 obliterate
Removes this caserun, its history, and all things that reference it.
=cut
sub obliterate {
my $self = shift;
my $single = shift;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("DELETE FROM test_case_bugs WHERE case_run_id = ?");
foreach my $id (@{$self->get_case_run_list}){
$sth->execute($id);
}
if ($single){
$dbh->do("DELETE FROM test_case_runs WHERE case_run_id = ?",
undef, ($self->id));
}
else {
$dbh->do("DELETE FROM test_case_runs WHERE case_id = ? AND run_id = ?",
undef, ($self->case_id, $self->run_id));
}
return 1;
}
###############################
#### Accessors ####
###############################
=head1 ACCESSOR METHODS
=head2 id
Returns the ID of the object
=head2 testedby
Returns a Bugzilla::User object representing the user that closed
this case-run.
=head2 assignee
Returns a Bugzilla::User object representing the user assigned to
update this case-run.
=head2 case_text_version
Returns the version of the test case document that this case-run
was run against.
=head2 notes
Returns the notes of the object
=head2 close_date
Returns the time stamp of when this case-run was closed
=head2 iscurrent
Returns true if this is the current case-run in the history list
=head2 status_id
Returns the status id of the object
=head2 sortkey
Returns the sortkey of the object
=head2 isprivate
Returns the true if this case-run is private.
=cut
=head2 updated_deps
Returns a reference to a list of dependent caseruns that were updated
=cut
sub id { return $_[0]->{'case_run_id'}; }
sub case_id { return $_[0]->{'case_id'}; }
sub run_id { return $_[0]->{'run_id'}; }
sub testedby { return Bugzilla::User->new($_[0]->{'testedby'}); }
sub assignee { return Bugzilla::User->new($_[0]->{'assignee'}); }
sub case_text_version { return $_[0]->{'case_text_version'}; }
sub running_date { return $_[0]->{'running_date'}; }
sub close_date { return $_[0]->{'close_date'}; }
sub iscurrent { return $_[0]->{'iscurrent'}; }
sub status_id { return $_[0]->{'case_run_status_id'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
sub isprivate { return $_[0]->{'isprivate'}; }
sub updated_deps { return $_[0]->{'updated_deps'}; }
=head2 type
Returns 'case'
=cut
sub type {
my $self = shift;
$self->{'type'} = 'caserun';
return $self->{'type'};
}
=head2 notes
Returns the cumulative notes of all caserun records of this case and run.
=cut
sub notes {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $notes = $dbh->selectcol_arrayref(
"SELECT notes
FROM test_case_runs
WHERE case_id = ? AND run_id = ?
ORDER BY case_run_id",
undef,($self->case_id, $self->run_id));
return join("\n", @$notes);
}
=head2 run
Returns the TestRun object that this case-run is associated with
=cut
# The potential exists for creating a circular reference here.
sub run {
my $self = shift;
return $self->{'run'} if exists $self->{'run'};
require Bugzilla::Testopia::TestRun;
$self->{'run'} = Bugzilla::Testopia::TestRun->new($self->{'run_id'});
return $self->{'run'};
}
=head2 case
Returns the TestCase object that this case-run is associated with
=cut
# The potential exists for creating a circular reference here.
sub case {
my $self = shift;
return $self->{'case'} if exists $self->{'case'};
require Bugzilla::Testopia::TestCase;
$self->{'case'} = Bugzilla::Testopia::TestCase->new($self->{'case_id'});
return $self->{'case'};
}
=head2 build
Returns the Build object that this case-run is associated with
=cut
sub build {
my $self = shift;
return $self->{'build'} if exists $self->{'build'};
$self->{'build'} = Bugzilla::Testopia::Build->new($self->{'build_id'});
return $self->{'build'};
}
=head2 environment
Returns the Build object that this case-run is associated with
=cut
sub environment {
my $self = shift;
return $self->{'environment'} if exists $self->{'environment'};
$self->{'environment'} = Bugzilla::Testopia::Environment->new($self->{'environment_id'});
return $self->{'environment'};
}
=head2 status
Looks up the status name of the associated status_id for this object
=cut
sub status {
my $self = shift;
my $dbh = Bugzilla->dbh;
($self->{'status'}) = $dbh->selectrow_array(
"SELECT name FROM test_case_run_status
WHERE case_run_status_id=?", undef,
$self->{'case_run_status_id'});
return $self->{'status'};
}
=head2 attachments
Returns a reference to a list of attachments associated with this
case.
=cut
sub attachments {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
return $self->{'attachments'} if exists $self->{'attachments'};
my $attachments = $dbh->selectall_arrayref(
"SELECT attachment_id, case_run_id
FROM test_case_attachments
WHERE case_id = ?",
{ Slice=>{} }, $self->case_id);
my @attachments;
foreach my $attach (@{$attachments}){
my $att = Bugzilla::Testopia::Attachment->new($attach->{attachment_id});
$att->{'caserun_id'} = $self->id if $attach->{case_run_id};
push @attachments, $att;
}
$self->{'attachments'} = \@attachments;
return $self->{'attachments'};
}
=head2 bugs
Returns a list of Bugzilla::Bug objects associated with this case-run
=cut
sub bugs {
my $self = shift;
#return $self->{'bug'} if exists $self->{'bug'};
my $dbh = Bugzilla->dbh;
my @bugs;
my $bugids = $dbh->selectcol_arrayref("SELECT bug_id
FROM test_case_bugs
WHERE case_run_id=?",
undef, $self->{'case_run_id'});
foreach my $bugid (@{$bugids}){
push @bugs, Bugzilla::Bug->new($bugid, Bugzilla->user->id) if Bugzilla->user->can_see_bug($bugid);
}
$self->{'bugs'} = \@bugs; #join(",", @$bugids);
return $self->{'bugs'};
}
=head2 bug_list
Returns a comma separated list of bug ids associated with this case-run
=cut
sub bug_list {
my $self = shift;
return $self->{'bug_list'} if exists $self->{'bug_list'};
my $dbh = Bugzilla->dbh;
my @bugs;
my $bugids = $dbh->selectcol_arrayref("SELECT DISTINCT bug_id
FROM test_case_bugs
WHERE case_run_id=?",
undef, $self->id);
my @visible;
foreach my $bugid (@{$bugids}){
push @visible, $bugid if Bugzilla->user->can_see_bug($bugid);
}
$self->{'bug_list'} = join(",", @$bugids);
return $self->{'bug_list'};
}
=head2 bug_count
Retuns a count of the bugs associated with this case-run
=cut
sub bug_count{
my $self = shift;
return $self->{'bug_count'} if exists $self->{'bug_count'};
my $dbh = Bugzilla->dbh;
$self->{'bug_count'} = $dbh->selectrow_array("SELECT COUNT(bug_id)
FROM test_case_bugs
WHERE case_run_id=?",
undef, $self->{'case_run_id'});
return $self->{'bug_count'};
}
=head2 get_buglist
Returns a comma separated string off bug ids associated with
this case-run
=cut
sub get_buglist {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $bugids = $dbh->selectcol_arrayref("SELECT bug_id
FROM test_case_bugs
WHERE case_run_id=?",
undef, $self->{'case_run_id'});
return join(',', @{$bugids});
}
=head2 is_open_status
Returns true if the status of this case-run is an open status
=cut
sub is_open_status {
my $self = shift;
my $status = shift;
my @open_status_list = (IDLE, RUNNING, PAUSED, ERROR);
return 1 if lsearch(\@open_status_list, $status) > -1;
}
=head2 is_closed_status
Returns true if the status of this case-run is a closed status
=cut
sub is_closed_status {
my $self = shift;
my $status = shift;
my @closed_status_list = (PASSED, FAILED, BLOCKED);
return 1 if lsearch(\@closed_status_list, $status) > -1;
}
=head2 canview
Returns true if the logged in user has rights to view this case-run.
=cut
sub canview {
my $self = shift;
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id) & TR_READ;
return 0;
}
=head2 canedit
Returns true if the logged in user has rights to edit this case-run.
=cut
sub canedit {
my $self = shift;
return 1 if Bugzilla->user->in_group('admin');
return 0 if $self->run->stop_date;
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id) & TR_ADMIN;
if ($self->status_id == RUNNING){
return 1 if $self->run->manager->id == Bugzilla->user->id;
return 0 unless $self->assignee->id && $self->assignee->id == Bugzilla->user->id;
}
return 1 if Bugzilla->user->in_group('Testers');
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id) & TR_WRITE;
return 0;
}
=head2 candelete
Returns true if the logged in user has rights to delete this case-run.
=cut
sub candelete {
my $self = shift;
return 1 if Bugzilla->user->in_group('admin');
return 0 unless Bugzilla->params->{"allow-test-deletion"};
return 1 if Bugzilla->user->in_group('Testers') && Bugzilla->params->{"testopia-allow-group-member-deletes"};
return 1 if $self->run->plan->get_user_rights(Bugzilla->user->id) & TR_DELETE;
return 0;
}
sub completion_time {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (str2time($self->running_date) && str2time($self->close_date)){
my $seconds = str2time($self->close_date) - str2time($self->running_date);
return $seconds;
}
return 0;
}
###############################
#### Functions ####
###############################
=head2 lookup_status
Returns the status name of the given case_run_status_id
=cut
sub lookup_status {
my ($status_id) = @_;
detaint_natural($status_id);
my $dbh = Bugzilla->dbh;
my ($status) = $dbh->selectrow_array(
"SELECT name
FROM test_case_run_status
WHERE case_run_status_id = ?",
undef, $status_id);
return $status;
}
=head2 lookup_status_by_name
Returns the id of the status name passed.
=cut
sub lookup_status_by_name {
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($value) = $dbh->selectrow_array(
"SELECT case_run_status_id
FROM test_case_run_status
WHERE name = ?",
undef, $name);
return $value;
}
1;
__END__
=head1 NAME
Bugzilla::Testopia::TestCaseRun - Testopia Test Case Run object
=head1 DESCRIPTION
This module represents a test case run in Testopia.
A test case run is a record in the test_case_runs table which joins
test cases to test runs. Basically, for each test run a selction of
test cases is made to be included in that run. As a test run
progresses, testers set statuses on each of the cases in the run.
If the build is changed on a case-run with a status, a clone of that
case-run is made in the table for historical purposes.
=head1 SYNOPSIS
use Bugzilla::Testopia::TestCaseRun;
$caserun = Bugzilla::Testopia::TestCaseRun->new($caserun_id);
$caserun = Bugzilla::Testopia::TestCaseRun->new(\%caserun_hash);
=cut
=head1 METHODS
=head2 new
Instantiate a new case run. This takes a single argument
either a test case ID or a reference to a hash containing keys
identical to a test case-run's fields and desired values.
=cut
=head2 _init
Private constructor for this object
=cut
=head1 SEE ALSO
TestCase TestRun
=head1 AUTHOR
Greg Hendricks <ghendricks@novell.com>
=cut