2476 lines
69 KiB
Perl
2476 lines
69 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 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.
|
|
#
|
|
# Portions taken from processbug.cgi and Bug.pm
|
|
# which are copyrighted by
|
|
# Terry Weissman <terry@mozilla.org>
|
|
# Dan Mosedale <dmose@mozilla.org>
|
|
# Dave Miller <justdave@syndicomm.com>
|
|
# Christopher Aillon <christopher@aillon.com>
|
|
# Myk Melez <myk@mozilla.org>
|
|
# Jeff Hedlund <jeff.hedlund@matrixsi.com>
|
|
# Frederic Buclin <LpSolit@gmail.com>
|
|
#
|
|
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
|
|
# Jeff Dayley <jedayley@novell.com>
|
|
# M-A Parent <maparent@miranda.com>
|
|
|
|
package Bugzilla::Extension::Testopia::TestCase;
|
|
|
|
use strict;
|
|
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Bug;
|
|
use Bugzilla::User;
|
|
use Bugzilla::Config;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Constants;
|
|
|
|
use Bugzilla::Extension::Testopia::Constants;
|
|
use Bugzilla::Extension::Testopia::Util;
|
|
use Bugzilla::Extension::Testopia::TestPlan;
|
|
use Bugzilla::Extension::Testopia::TestRun;
|
|
use Bugzilla::Extension::Testopia::TestCaseRun;
|
|
use Bugzilla::Extension::Testopia::Category;
|
|
use Bugzilla::Extension::Testopia::Attachment;
|
|
|
|
use JSON;
|
|
use Text::Diff;
|
|
|
|
use base qw(Exporter Bugzilla::Object);
|
|
our @EXPORT = qw(lookup_status lookup_status_by_name
|
|
lookup_category lookup_category_by_name
|
|
lookup_priority lookup_priority_by_value
|
|
lookup_default_tester);
|
|
|
|
###############################
|
|
#### Initialization ####
|
|
###############################
|
|
|
|
use constant DB_TABLE => "test_cases";
|
|
use constant NAME_FIELD => "alias";
|
|
use constant ID_FIELD => "case_id";
|
|
use constant DB_COLUMNS => qw(
|
|
case_id
|
|
case_status_id
|
|
category_id
|
|
priority_id
|
|
author_id
|
|
default_tester_id
|
|
creation_date
|
|
estimated_time
|
|
isautomated
|
|
sortkey
|
|
script
|
|
arguments
|
|
summary
|
|
requirement
|
|
alias
|
|
);
|
|
|
|
use constant REQUIRED_CREATE_FIELDS => qw(case_status_id category_id priority_id author_id summary plans);
|
|
use constant UPDATE_COLUMNS => qw(case_status_id category_id priority_id default_tester_id
|
|
isautomated sortkey script arguments summary requirement
|
|
alias estimated_time dependson blocks runs tags components);
|
|
|
|
use constant VALIDATORS => {
|
|
case_status_id => \&_check_status,
|
|
priority_id => \&_check_priority,
|
|
default_tester_id => \&_check_tester,
|
|
author_id => \&_check_author,
|
|
isautomated => \&_check_automated,
|
|
sortkey => \&_check_sortkey,
|
|
script => \&_check_script,
|
|
arguments => \&_check_arguments,
|
|
summary => \&_check_summary,
|
|
requirement => \&_check_requirements,
|
|
alias => \&_check_alias,
|
|
estimated_time => \&_check_time,
|
|
dependson => \&_check_dependency,
|
|
blocked => \&_check_dependency,
|
|
plans => \&_check_plans,
|
|
runs => \&_check_runs,
|
|
tags => \&_check_tags,
|
|
components => \&_check_components,
|
|
bugs => \&_check_bugs,
|
|
|
|
};
|
|
|
|
use constant ALIAS_MAX_LENGTH => 255;
|
|
use constant REQUIREMENT_MAX_LENGTH => 255;
|
|
use constant SUMMARY_MAX_LENGTH => 255;
|
|
use constant TAG_MAX_LENGTH => 255;
|
|
|
|
sub display_columns {
|
|
my $self = shift;
|
|
my @columns =
|
|
[{column => 'case_id', desc => 'ID' },
|
|
{column => 'case_status_id', desc => 'Status' },
|
|
{column => 'category_id', desc => 'Category' },
|
|
{column => 'priority_id', desc => 'Priority' },
|
|
{column => 'summary', desc => 'Summary' },
|
|
{column => 'requirement', desc => 'Requirement' },
|
|
{column => 'alias', desc => 'Alias' }];
|
|
|
|
$self->{'display_columns'} = \@columns;
|
|
return $self->{'display_columns'};
|
|
}
|
|
|
|
sub report_columns {
|
|
my $self = shift;
|
|
my %columns;
|
|
# Changes here need to match Report.pm
|
|
$columns{'Status'} = "case_status";
|
|
$columns{'Priority'} = "priority";
|
|
$columns{'Product'} = "product";
|
|
$columns{'Component'} = "component";
|
|
$columns{'Category'} = "category";
|
|
$columns{'Automated'} = "isautomated";
|
|
$columns{'Tags'} = "tags";
|
|
$columns{'Requirement'} = "requirement";
|
|
$columns{'Author'} = "author";
|
|
$columns{'Default tester'} = "default_tester";
|
|
my @result;
|
|
push @result, {'name' => $_, 'id' => $columns{$_}} foreach (sort(keys %columns));
|
|
unshift @result, {'name' => '<none>', 'id'=> ''};
|
|
return \@result;
|
|
|
|
}
|
|
|
|
###############################
|
|
#### Validators ####
|
|
###############################
|
|
|
|
sub _check_status{
|
|
my ($invocant, $status) = @_;
|
|
$status = trim($status);
|
|
my $status_id;
|
|
if ($status =~ /^\d+$/){
|
|
$status_id = Bugzilla::Extension::Testopia::Util::validate_selection($status, 'case_status_id', 'test_case_status');
|
|
}
|
|
else {
|
|
trick_taint($status);
|
|
$status_id = lookup_status_by_name($status);
|
|
}
|
|
ThrowUserError('invalid_status') unless $status_id;
|
|
return $status_id;
|
|
}
|
|
|
|
sub _check_category{
|
|
my ($invocant, $category, $product) = @_;
|
|
$category = trim($category);
|
|
my $category_id;
|
|
if (ref $category){
|
|
$product = Bugzilla::Product->check($category->{'product'});
|
|
$category_id = Bugzilla::Extension::Testopia::Category::check_case_category($category->{'category'}, $product);
|
|
}
|
|
elsif ($category =~ /^\d+$/){
|
|
$category_id = Bugzilla::Extension::Testopia::Util::validate_selection($category, 'category_id', 'test_case_categories');
|
|
}
|
|
else {
|
|
$category_id = Bugzilla::Extension::Testopia::Category::check_case_category($category, $product);
|
|
}
|
|
|
|
return $category_id;
|
|
}
|
|
|
|
sub _check_priority{
|
|
my ($invocant, $priority) = @_;
|
|
$priority = trim($priority);
|
|
trick_taint($priority);
|
|
my $priority_id;
|
|
if ($priority =~ /^\d+$/){
|
|
$priority_id = Bugzilla::Extension::Testopia::Util::validate_selection($priority, 'id', 'priority');
|
|
}
|
|
else {
|
|
$priority_id = lookup_priority_by_value($priority);
|
|
}
|
|
ThrowCodeError('bad_arg', {argument => 'priority', function => 'set_priority'}) unless $priority_id;
|
|
return $priority_id;
|
|
}
|
|
|
|
sub _check_tester{
|
|
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;
|
|
}
|
|
}
|
|
|
|
sub _check_author{
|
|
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;
|
|
}
|
|
}
|
|
|
|
sub _check_automated{
|
|
my ($invocant, $isactive) = @_;
|
|
$isactive = trim($isactive);
|
|
ThrowCodeError('bad_arg', {argument => 'isautomated', function => 'set_automated'}) unless ($isactive =~ /(1|0)/);
|
|
return $isactive;
|
|
}
|
|
|
|
sub _check_sortkey{
|
|
my ($invocant, $sortkey) = @_;
|
|
$sortkey = trim($sortkey);
|
|
return unless $sortkey;
|
|
ThrowCodeError('bad_arg', {argument => 'sortkey', function => 'set_sortkey'}) unless ($sortkey =~ /^\d+$/);
|
|
return $sortkey;
|
|
}
|
|
|
|
sub _check_script{
|
|
my ($invocant, $value) = @_;
|
|
return $value;
|
|
}
|
|
|
|
sub _check_arguments{
|
|
my ($invocant, $value) = @_;
|
|
return $value;
|
|
}
|
|
|
|
sub _check_summary{
|
|
my ($invocant, $summary) = @_;
|
|
$summary = clean_text($summary) if $summary;
|
|
|
|
if (!defined $summary || $summary eq '') {
|
|
ThrowUserError('testopia-missing-required-field', {'field' => 'summary'});
|
|
}
|
|
return $summary;
|
|
}
|
|
|
|
sub _check_requirements{
|
|
my ($invocant, $value) = @_;
|
|
return $value;
|
|
}
|
|
|
|
sub _check_alias {
|
|
my ($invocant, $alias) = @_;
|
|
$alias = trim($alias);
|
|
return unless $alias;
|
|
trick_taint($alias);
|
|
my $id;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $query = "SELECT case_id
|
|
FROM test_cases
|
|
WHERE alias = ?";
|
|
|
|
# If this is a new test case then we only need to check if another test case
|
|
# has this alias already. If we are updating the test case though, we know
|
|
# there is already at least one that has this alias - this case.
|
|
$query .= " AND case_id != ?" if ref $invocant;
|
|
|
|
if (ref $invocant){
|
|
($id) = $dbh->selectrow_array($query, undef, ($alias, $invocant->id));
|
|
}
|
|
else {
|
|
($id) = $dbh->selectrow_array($query, undef, $alias);
|
|
}
|
|
ThrowUserError('testiopia-alias-exists', {'alias' => $alias}) if $id;
|
|
return $alias;
|
|
}
|
|
|
|
sub _check_time{
|
|
my ($invocant, $time) = @_;
|
|
$time = trim($time);
|
|
return '0:0:0' unless $time;
|
|
$time =~ m/^(\d+)[:\s](\d+)[:\s](\d+)$/;
|
|
ThrowUserError('testopia-format-error', {'field' => 'Estimated Time' })
|
|
unless (defined $1 && defined $2 && $2 < 60 && defined $3 && $3 < 60);
|
|
$time = "$1:$2:$3";
|
|
return $time;
|
|
}
|
|
|
|
sub _check_dependency{
|
|
my ($invocant, $value) = @_;
|
|
$value = trim($value);
|
|
if ($value) {
|
|
my @validvalues;
|
|
foreach my $id (split(/[\s,]+/, $value)) {
|
|
next unless $id;
|
|
Bugzilla::Extension::Testopia::Util::validate_test_id($id, 'case');
|
|
push(@validvalues, $id);
|
|
}
|
|
$value = join(",", @validvalues);
|
|
return $value;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub _check_plans {
|
|
my ($invocant, $plans) = @_;
|
|
# $plans is a reference to an array of Bugzilla::Extension::Testopia::TestPlan objects.
|
|
ThrowUserError('plan_needed') unless scalar @$plans > 0;
|
|
return $plans;
|
|
}
|
|
|
|
sub _check_cases {
|
|
my ($invocant, $caseids) = @_;
|
|
my @cases;
|
|
|
|
foreach my $caseid (split(/[\s,]+/, $caseids)){
|
|
Bugzilla::Extension::Testopia::Util::validate_test_id($caseid, 'case');
|
|
push @cases, Bugzilla::Extension::Testopia::TestCase->new($caseid);
|
|
}
|
|
|
|
return \@cases;
|
|
}
|
|
|
|
sub _check_runs {
|
|
my ($invocant, $runids) = @_;
|
|
my @runs;
|
|
if (ref $runids eq 'ARRAY'){
|
|
$runids = join(',' ,@$runids);
|
|
}
|
|
foreach my $runid (split(/[\s,]+/, $runids)){
|
|
Bugzilla::Extension::Testopia::Util::validate_test_id($runid, 'run');
|
|
push @runs, Bugzilla::Extension::Testopia::TestRun->new($runid);
|
|
}
|
|
return \@runs;
|
|
}
|
|
|
|
sub _check_tags {
|
|
my ($invocant, $tags) = @_;
|
|
|
|
return $tags;
|
|
}
|
|
|
|
sub _check_components {
|
|
my ($invocant, $components) = @_;
|
|
my @components;
|
|
my @comp_ids;
|
|
my $dbh = Bugzilla->dbh;
|
|
ThrowUserError('testopia-missing-parameter', {param => 'components'}) unless $components;
|
|
if (ref $components eq 'HASH'){
|
|
my $prod = Bugzilla::Product->check($components->{'product'});
|
|
my $comp = Bugzilla::Component->check({product=> $prod, name => $components->{'component'}});
|
|
push @comp_ids, $comp->id;
|
|
}
|
|
elsif (ref $components eq 'ARRAY'){
|
|
foreach my $c (@$components){
|
|
if (ref $c){
|
|
my $prod = Bugzilla::Product->check($c->{'product'});
|
|
my $comp = Bugzilla::Component->check({product => $prod, name => $c->{'component'}});
|
|
push @comp_ids, $comp->id;
|
|
}
|
|
else{
|
|
validate_selection($c,'id','components');
|
|
push @comp_ids, $c;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
@comp_ids = split(/[\s,]+/, $components);
|
|
}
|
|
foreach my $id (@comp_ids){
|
|
Bugzilla::Extension::Testopia::Util::validate_selection($id, 'id', 'components');
|
|
trick_taint($id);
|
|
|
|
if (ref $invocant){
|
|
my ($is) = $dbh->selectrow_array(
|
|
"SELECT case_id FROM test_case_components
|
|
WHERE case_id = ? AND component_id = ?",
|
|
undef, ($invocant->id, $id));
|
|
ThrowUserError('testopia_component_attached') if $is;
|
|
}
|
|
|
|
push @components, $id;
|
|
}
|
|
return \@components;
|
|
}
|
|
|
|
sub _check_bugs {
|
|
my ($invocant, $bugids, $attach) = @_;
|
|
my @bugids;
|
|
my @ids;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
if (ref $bugids eq 'ARRAY'){
|
|
push @ids, @$bugids;
|
|
}
|
|
else {
|
|
push @ids, split(/[\s,]+/, $bugids);
|
|
}
|
|
|
|
foreach my $bug (@ids){
|
|
trick_taint($bug);
|
|
Bugzilla::Bug->check($bug);
|
|
if (ref $invocant && $attach){
|
|
my ($exists) = $dbh->selectrow_array(
|
|
"SELECT bug_id
|
|
FROM test_case_bugs
|
|
WHERE case_id=?
|
|
AND bug_id=?",
|
|
undef, ($invocant->id, $bug));
|
|
next if ($exists);
|
|
}
|
|
push @bugids, $bug;
|
|
}
|
|
|
|
return \@bugids;
|
|
}
|
|
|
|
###############################
|
|
#### Mutators ####
|
|
###############################
|
|
sub set_case_status { $_[0]->set('case_status_id', $_[1]); }
|
|
sub set_priority { $_[0]->set('priority_id', $_[1]); }
|
|
sub set_default_tester { $_[0]->set('default_tester_id', $_[1]); }
|
|
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
|
sub set_requirement { $_[0]->set('requirement', $_[1]); }
|
|
sub set_isautomated { $_[0]->set('isautomated', $_[1]); }
|
|
sub set_script { $_[0]->set('script', $_[1]); }
|
|
sub set_arguments { $_[0]->set('arguments', $_[1]); }
|
|
sub set_summary { $_[0]->set('summary', $_[1]); }
|
|
sub set_alias { $_[0]->set('alias', $_[1]); }
|
|
sub set_estimated_time { $_[0]->set('estimated_time', $_[1]); }
|
|
sub set_dependson { $_[0]->set('dependson', $_[1]); }
|
|
sub set_blocks { $_[0]->set('blocks', $_[1]); }
|
|
|
|
sub set_category {
|
|
my ($self, $value) = @_;
|
|
$value = $self->_check_category($value, $self->plans->[0]->product);
|
|
$self->set('category_id', $value);
|
|
}
|
|
|
|
sub new {
|
|
my $invocant = shift;
|
|
my $class = ref($invocant) || $invocant;
|
|
my $param = shift;
|
|
|
|
# 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' && !keys %$param){
|
|
bless($param, $class);
|
|
return $param;
|
|
}
|
|
|
|
if (!defined $param || (!ref($param) && $param !~ /^\d+$/)) {
|
|
$param = { name => $param };
|
|
}
|
|
|
|
unshift @_, $param;
|
|
my $self = $class->SUPER::new(@_);
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub run_create_validators {
|
|
my $class = shift;
|
|
my $params = $class->SUPER::run_create_validators(@_);
|
|
my $product = $params->{plans}->[0]->product;
|
|
|
|
$params->{category_id} = $class->_check_category($params->{category_id}, $product);
|
|
|
|
return $params;
|
|
}
|
|
|
|
sub run_import_validators {
|
|
my $class = shift;
|
|
my $params = $class->SUPER::run_create_validators(@_);
|
|
|
|
return $params;
|
|
}
|
|
|
|
sub create {
|
|
my ($class, $params) = @_;
|
|
|
|
$class->SUPER::check_required_create_fields($params);
|
|
my $field_values = $class->run_create_validators($params);
|
|
|
|
$field_values->{creation_date} = Bugzilla::Extension::Testopia::Util::get_time_stamp();
|
|
|
|
# We have to handle these fields a bit differently since they have their own tables.
|
|
my $action = $field_values->{action};
|
|
my $effect = $field_values->{effect};
|
|
my $setup = $field_values->{setup};
|
|
my $breakdown = $field_values->{breakdown};
|
|
my $dependson = $field_values->{dependson};
|
|
my $blocks = $field_values->{blocks};
|
|
my $plans = $field_values->{plans};
|
|
my $runs = $field_values->{runs};
|
|
my $component = $field_values->{components};
|
|
my $tags = $field_values->{tags};
|
|
my $bugs = $field_values->{bugs};
|
|
|
|
# Since these are not part of a case strictly speaking, we remove them before
|
|
# calling insert_create_data.
|
|
foreach my $field (qw(action effect setup breakdown dependson blocks plans runs components tags bugs)){
|
|
delete $field_values->{$field};
|
|
}
|
|
|
|
my $self = $class->SUPER::insert_create_data($field_values);
|
|
|
|
$self->store_text($self->id, $field_values->{'author_id'}, $action, $effect,
|
|
$setup, $breakdown,0);
|
|
|
|
$self->update_deps($dependson, $blocks, $self->id);
|
|
|
|
foreach my $p (@$plans){
|
|
$self->link_plan($p->id, $self->id);
|
|
}
|
|
delete $self->{'plans'};
|
|
|
|
foreach my $run (@$runs){
|
|
$run->add_case_run($self->id, $self->sortkey);
|
|
}
|
|
|
|
$self->add_component($component, "VALIDATED");
|
|
$self->add_tag($tags);
|
|
$self->attach_bug($bugs);
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub update {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $timestamp = Bugzilla::Extension::Testopia::Util::get_time_stamp();
|
|
|
|
$self->update_deps($self->{'dependson'}, $self->{'blocks'});
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
my $changed = $self->SUPER::update();
|
|
|
|
delete $changed->{'sortkey'};
|
|
|
|
foreach my $field (keys %$changed){
|
|
Bugzilla::Extension::Testopia::Util::log_activity('case', $self->id, $field, $timestamp,
|
|
$changed->{$field}->[0], $changed->{$field}->[1]);
|
|
}
|
|
|
|
$dbh->bz_commit_transaction();
|
|
}
|
|
|
|
###############################
|
|
#### Functions ####
|
|
###############################
|
|
|
|
sub lookup_status {
|
|
my ($id) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
detaint_natural($id);
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT name
|
|
FROM test_case_status
|
|
WHERE case_status_id = ?",
|
|
undef, $id);
|
|
return $value;
|
|
}
|
|
|
|
sub lookup_status_by_name {
|
|
my ($name) = @_;
|
|
trick_taint($name);
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT case_status_id
|
|
FROM test_case_status
|
|
WHERE name = ?",
|
|
undef, $name);
|
|
return $value;
|
|
}
|
|
|
|
sub lookup_category {
|
|
my ($id) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
detaint_natural($id);
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT name
|
|
FROM test_case_categories
|
|
WHERE category_id = ?",
|
|
undef, $id);
|
|
return $value;
|
|
}
|
|
|
|
sub lookup_category_by_name {
|
|
my ($name) = @_;
|
|
trick_taint($name);
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT category_id
|
|
FROM test_case_categories
|
|
WHERE name = ?",
|
|
undef, $name);
|
|
return $value;
|
|
}
|
|
|
|
sub lookup_priority {
|
|
my ($id) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
detaint_natural($id);
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT value
|
|
FROM priority
|
|
WHERE id = ?",
|
|
undef, $id);
|
|
return $value;
|
|
}
|
|
|
|
sub lookup_priority_by_value {
|
|
my ($value) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
trick_taint($value);
|
|
my ($id) = $dbh->selectrow_array(
|
|
"SELECT id
|
|
FROM priority
|
|
WHERE value = ?",
|
|
undef, $value);
|
|
return $id;
|
|
}
|
|
|
|
sub lookup_default_tester {
|
|
my ($id) = @_;
|
|
detaint_natural($id) or return '';
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($value) = $dbh->selectrow_array(
|
|
"SELECT login_name
|
|
FROM profiles
|
|
WHERE userid = ?",
|
|
undef, $id);
|
|
return $value;
|
|
}
|
|
|
|
###############################
|
|
#### Methods ####
|
|
###############################
|
|
sub get_selectable_components {
|
|
my $self = shift;
|
|
my ($byid) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my @exclusions;
|
|
unless ($byid) {
|
|
foreach my $e (@{$self->components}){
|
|
push @exclusions, $e->{'id'};
|
|
}
|
|
}
|
|
my $query = "SELECT id FROM components
|
|
WHERE product_id IN (" . join(",", @{$self->get_product_ids}) . ") ";
|
|
if (@exclusions){
|
|
$query .= "AND id NOT IN(". join(",", @exclusions) .") ";
|
|
}
|
|
$query .= "ORDER BY name";
|
|
|
|
my $ref = $dbh->selectcol_arrayref($query);
|
|
my @comps;
|
|
push @comps, {'id' => '0', 'name' => '--Please Select--'} unless $byid;
|
|
foreach my $id (@$ref){
|
|
push @comps, Bugzilla::Component->new($id);
|
|
}
|
|
return \@comps;
|
|
}
|
|
|
|
=head2 get_category_list
|
|
|
|
Returns a list of categories associated with products in all
|
|
plans referenced by this case.
|
|
|
|
=cut
|
|
|
|
sub get_category_list{
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $ids = $dbh->selectcol_arrayref(
|
|
"SELECT category_id
|
|
FROM test_case_categories
|
|
WHERE product_id IN (". join(",", @{$self->get_product_ids}) .")");
|
|
my @categories;
|
|
foreach my $c (@$ids){
|
|
push @categories, Bugzilla::Extension::Testopia::Category->new($c);
|
|
}
|
|
return \@categories;
|
|
}
|
|
|
|
=head2 get_product_ids
|
|
|
|
Returns the list of product ids that this case is associated with
|
|
|
|
=cut
|
|
|
|
sub get_product_ids {
|
|
my $self = shift;
|
|
|
|
if ($self->id == 0){
|
|
my @ids;
|
|
foreach my $plan (@{$self->plans}){
|
|
push @ids, $plan->product_id if Bugzilla->user->can_see_product($plan->product->name);
|
|
}
|
|
return \@ids;
|
|
}
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $ref = $dbh->selectcol_arrayref(
|
|
"SELECT DISTINCT products.id FROM products
|
|
JOIN test_plans AS plans ON plans.product_id = products.id
|
|
JOIN test_case_plans ON plans.plan_id = test_case_plans.plan_id
|
|
JOIN test_cases AS cases ON cases.case_id = test_case_plans.case_id
|
|
WHERE cases.case_id = ?
|
|
ORDER BY products.id", undef, $self->{'case_id'});
|
|
return $ref;
|
|
}
|
|
|
|
=head2 get_status_list
|
|
|
|
Returns the list of legal statuses for a test case
|
|
|
|
=cut
|
|
|
|
sub get_status_list {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $ref = $dbh->selectall_arrayref("
|
|
SELECT case_status_id AS id, name
|
|
FROM test_case_status", {"Slice"=>{}});
|
|
return $ref
|
|
}
|
|
|
|
=head2 get_text_versions
|
|
|
|
Returns the list of versions of the plan document.
|
|
|
|
=cut
|
|
|
|
sub get_text_versions {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $versions = $dbh->selectall_arrayref(
|
|
"SELECT case_text_version AS id, case_text_version AS name
|
|
FROM test_case_texts
|
|
WHERE case_id = ?
|
|
ORDER BY case_text_version",
|
|
{'Slice' =>{}}, $self->id);
|
|
return $versions;
|
|
}
|
|
|
|
=head2 get_priority_list
|
|
|
|
Returns a list of legal priorities
|
|
|
|
=cut
|
|
|
|
sub get_priority_list {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $ref = $dbh->selectall_arrayref("
|
|
SELECT id, value AS name FROM priority
|
|
ORDER BY sortkey", {"Slice"=>{}});
|
|
return $ref
|
|
}
|
|
|
|
=head2 get_caserun_count
|
|
|
|
Takes a status and returns the count of that status
|
|
|
|
=cut
|
|
|
|
sub get_caserun_count {
|
|
my $self = shift;
|
|
my ($status) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $query = "SELECT COUNT(*)
|
|
FROM test_case_runs
|
|
WHERE case_id = ? ";
|
|
$query .= "AND case_run_status_id = ?" if $status;
|
|
|
|
my $count;
|
|
if ($status){
|
|
($count) = $dbh->selectrow_array($query,undef,($self->id,$status));
|
|
}
|
|
else {
|
|
($count) = $dbh->selectrow_array($query,undef,$self->id);
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
=head2 add_tag
|
|
|
|
Associates a tag with this test case
|
|
|
|
=cut
|
|
|
|
sub add_tag {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my @tags;
|
|
foreach my $t (@_){
|
|
if (ref $t eq 'ARRAY'){
|
|
push @tags, $_ foreach @$t;
|
|
}
|
|
else {
|
|
push @tags, split(/,+/, $t);
|
|
}
|
|
}
|
|
|
|
foreach my $name (@tags){
|
|
my $tag = Bugzilla::Extension::Testopia::TestTag->create({'tag_name' => $name});
|
|
$tag->attach($self);
|
|
}
|
|
}
|
|
|
|
=head2 remove_tag
|
|
|
|
Disassociates a tag from this test case
|
|
|
|
=cut
|
|
|
|
sub remove_tag {
|
|
my $self = shift;
|
|
my ($tag_name) = @_;
|
|
my $tag = Bugzilla::Extension::Testopia::TestTag->check_tag($tag_name);
|
|
ThrowUserError('testopia-unknown-tag', {'name' => $tag}) unless $tag;
|
|
my $dbh = Bugzilla->dbh;
|
|
$dbh->do("DELETE FROM test_case_tags
|
|
WHERE tag_id=? AND case_id=?",
|
|
undef, $tag->id, $self->{'case_id'});
|
|
return;
|
|
}
|
|
|
|
=head2 attach_bug
|
|
|
|
Attaches the specified bug to this test case
|
|
|
|
=cut
|
|
|
|
sub attach_bug {
|
|
my $self = shift;
|
|
my ($bugids, $caserun_id) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
trick_taint($caserun_id) if $caserun_id;
|
|
|
|
$bugids = $self->_check_bugs($bugids, "ATTACH");
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
foreach my $bug (@$bugids){
|
|
$dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id, case_id)
|
|
VALUES(?,?,?)", undef, ($bug, $caserun_id, $self->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 ($bugids) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
$bugids = $self->_check_bugs($bugids);
|
|
|
|
foreach my $bug (@$bugids){
|
|
$dbh->do("DELETE FROM test_case_bugs
|
|
WHERE bug_id = ?
|
|
AND case_id = ?",
|
|
undef, ($bug, $self->{'case_id'}));
|
|
}
|
|
}
|
|
|
|
=head2 add_component
|
|
|
|
Associates a component with this test case
|
|
|
|
=cut
|
|
|
|
sub add_component {
|
|
my $self = shift;
|
|
my ($compids, $validated) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $comps = $validated ? $compids : $self->_check_components($compids);
|
|
foreach my $id (@$comps){
|
|
$dbh->do("INSERT INTO test_case_components (case_id, component_id)
|
|
VALUES (?,?)",undef, $self->{'case_id'}, $id);
|
|
}
|
|
|
|
delete $self->{'components'};
|
|
}
|
|
|
|
sub add_to_run {
|
|
my $self = shift;
|
|
my ($runids) = @_;
|
|
my $runs = $self->_check_runs($runids);
|
|
|
|
foreach my $run (@$runs){
|
|
$run->add_case_run($self->id, $self->sortkey);
|
|
}
|
|
}
|
|
|
|
=head2 add_blocks
|
|
|
|
Adds a list of test cases to the current test cases being blocked by the testcase
|
|
|
|
=cut
|
|
|
|
sub add_blocks {
|
|
my $self = shift;
|
|
my $case_ids = shift;
|
|
|
|
my $cases = $self->_check_cases($case_ids);
|
|
my @blocks;
|
|
|
|
my $dbh = Bugzilla->dbh();
|
|
|
|
# Get a list of the cases this test case currently blocks
|
|
my $current_blocks = $dbh->selectcol_arrayref("SELECT blocked
|
|
FROM test_case_dependencies
|
|
WHERE dependson = ?",
|
|
undef,
|
|
$self->id());
|
|
|
|
# update the list of items
|
|
foreach (@$cases) {
|
|
push @blocks, $_->id();
|
|
}
|
|
|
|
push @blocks, @$current_blocks;
|
|
|
|
$cases = join ",", @blocks;
|
|
|
|
$self->set_blocks($cases);
|
|
$self->update();
|
|
}
|
|
|
|
=head2 remove_blocks
|
|
|
|
Removes a list of test cases from being blocked by the testcase
|
|
|
|
=cut
|
|
|
|
sub remove_blocks {
|
|
my $self = shift;
|
|
my $case_ids = shift;
|
|
|
|
return 0 if ($case_ids eq '' or !defined $case_ids);
|
|
|
|
my $dbh = Bugzilla->dbh();
|
|
|
|
my @cases;
|
|
|
|
foreach my $case (split /[\s,]+/, $case_ids){
|
|
detaint_natural($case);
|
|
push @cases, $case;
|
|
}
|
|
|
|
my $query ="DELETE
|
|
FROM test_case_dependencies
|
|
WHERE dependson = ?";
|
|
|
|
$query .= "AND (";
|
|
$query .= join(" or ", map {"blocked = ?"} @cases);
|
|
$query .= ")";
|
|
|
|
$dbh->do($query, undef, $self->id, @cases);
|
|
}
|
|
|
|
=head2 add_dependson
|
|
|
|
Adds a list of test cases to the current test cases depended on by the testcase
|
|
|
|
=cut
|
|
|
|
sub add_dependson {
|
|
my $self = shift;
|
|
my $case_ids = shift;
|
|
|
|
my $cases = $self->_check_cases($case_ids);
|
|
my @dependson;
|
|
|
|
my $dbh = Bugzilla->dbh();
|
|
|
|
# Get a list of the cases this test case currently blocks
|
|
my $current_dependson = $dbh->selectcol_arrayref("SELECT dependson
|
|
FROM test_case_dependencies
|
|
WHERE blocked = ?",
|
|
undef,
|
|
$self->id());
|
|
|
|
# update the list of items
|
|
foreach (@$cases) {
|
|
push @dependson, $_->id();
|
|
}
|
|
|
|
push @dependson, @$current_dependson;
|
|
|
|
$cases = join ",", @dependson;
|
|
|
|
$self->set_dependson($cases);
|
|
$self->update();
|
|
}
|
|
|
|
=head2 remove_dependson
|
|
|
|
Adds a list of test cases to the current test cases depended on by the testcase
|
|
|
|
=cut
|
|
|
|
sub remove_dependson {
|
|
my $self = shift;
|
|
my $case_ids = shift;
|
|
|
|
return 0 if ($case_ids eq '' or !defined $case_ids);
|
|
|
|
my $dbh = Bugzilla->dbh();
|
|
|
|
my @cases;
|
|
|
|
foreach my $case (split /[\s,]+/, $case_ids)
|
|
{
|
|
detaint_natural($case);
|
|
push @cases, $case;
|
|
}
|
|
|
|
my $query = "
|
|
DELETE
|
|
FROM test_case_dependencies
|
|
WHERE blocked = ?";
|
|
|
|
|
|
$query .= "AND (";
|
|
$query .= join(" or ", map {"dependson = ?"} @cases);
|
|
$query .= ")";
|
|
|
|
$dbh->do($query, undef, $self->id, @cases);
|
|
}
|
|
|
|
=head2 remove_component
|
|
|
|
Disassociates a component with this test case
|
|
|
|
=cut
|
|
|
|
sub remove_component {
|
|
my $self = shift;
|
|
my ($comp_id) = @_;
|
|
trick_taint($comp_id);
|
|
my $dbh = Bugzilla->dbh;
|
|
$dbh->do("DELETE FROM test_case_components
|
|
WHERE case_id = ? AND component_id = ?",
|
|
undef, $self->{'case_id'}, $comp_id);
|
|
}
|
|
|
|
=head2 compare_doc_versions
|
|
|
|
Returns a unified diff of two versions of a case document (action
|
|
and effect). It takes two arguments both integers representing
|
|
the first and second versions to compare.
|
|
|
|
=cut
|
|
|
|
sub compare_doc_versions {
|
|
my $self = shift;
|
|
my ($newversion, $oldversion) = @_;
|
|
detaint_natural($newversion);
|
|
detaint_natural($oldversion);
|
|
my $dbh = Bugzilla->dbh;
|
|
my %diff;
|
|
my ($newaction, $neweffect, $newsetup, $newbreakdown) = $dbh->selectrow_array(
|
|
"SELECT action, effect, setup, breakdown FROM test_case_texts
|
|
WHERE case_id = ? AND case_text_version = ?",
|
|
undef, $self->{'case_id'}, $newversion);
|
|
|
|
my ($oldaction, $oldeffect, $oldsetup, $oldbreakdown) = $dbh->selectrow_array(
|
|
"SELECT action, effect, setup, breakdown FROM test_case_texts
|
|
WHERE case_id = ? AND case_text_version = ?",
|
|
undef, $self->{'case_id'}, $oldversion);
|
|
$diff{'action'} = diff(\$newaction, \$oldaction);
|
|
$diff{'effect'} = diff(\$neweffect, \$oldeffect);
|
|
$diff{'setup'} = diff(\$newsetup, \$oldsetup);
|
|
$diff{'breakdown'} = diff(\$newbreakdown, \$oldbreakdown);
|
|
return \%diff;
|
|
}
|
|
|
|
=head2 diff_case_doc
|
|
|
|
Returns the diff of the latest case document (action and effect)
|
|
with some text passed as an argument. Used to determine if the
|
|
text has changed and thus requiring a new version be created.
|
|
|
|
=cut
|
|
|
|
sub diff_case_doc {
|
|
my $self = shift;
|
|
my ($newaction, $neweffect, $newsetup, $newbreakdown) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($oldaction, $oldeffect, $oldsetup, $oldbreakdown) = $dbh->selectrow_array(
|
|
"SELECT action, effect, setup, breakdown
|
|
FROM test_case_texts
|
|
WHERE case_id = ? AND case_text_version = ?",
|
|
undef, ($self->{'case_id'}, $self->version));
|
|
my $diff = diff(\$newaction, \$oldaction);
|
|
$diff .= diff(\$neweffect, \$oldeffect);
|
|
$diff .= diff(\$newsetup, \$oldsetup);
|
|
$diff .= diff(\$newbreakdown, \$oldbreakdown);
|
|
return $diff
|
|
}
|
|
|
|
=head2 get_fields
|
|
|
|
Returns a reference to a list of test case field descriptions from
|
|
the test_fielddefs table.
|
|
|
|
=cut
|
|
|
|
sub get_fields {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $types = $dbh->selectall_arrayref(
|
|
"SELECT fieldid AS id, description AS name
|
|
FROM test_fielddefs
|
|
WHERE table_name=?",
|
|
{"Slice"=>{}}, "test_cases");
|
|
unshift @$types, {id => 'text', name => 'Text (Action, Setup, etc.)'};
|
|
unshift @$types, {id => '[Creation]', name => '[Created]'};
|
|
return $types;
|
|
}
|
|
|
|
=head2 store
|
|
|
|
Stores a test case object in the database. This method is used to store a
|
|
newly created test case. It returns the new ID.
|
|
|
|
=cut
|
|
|
|
sub store {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
# Exclude the auto-incremented field from the column list.
|
|
my $columns = join(", ", grep {$_ ne 'case_id'} DB_COLUMNS);
|
|
my ($timestamp) = Bugzilla::Extension::Testopia::Util::get_time_stamp();
|
|
|
|
$dbh->do("INSERT INTO test_cases ($columns) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
undef, ## Database Column ##
|
|
($self->{'case_status_id'}, # case_status_id
|
|
$self->{'category_id'}, # category_id
|
|
$self->{'priority_id'}, # priority_id
|
|
$self->{'author_id'}, # author_id
|
|
$self->{'default_tester_id'}, # default_tester_id
|
|
$timestamp, # creation_date
|
|
$self->{'estimated_time'}, # estimated_time
|
|
$self->{'isautomated'}, # isautomated
|
|
$self->sortkey, # sortkey
|
|
$self->{'script'}, # script
|
|
$self->{'arguments'}, # arguments
|
|
$self->{'summary'}, # summary
|
|
$self->{'requirement'}, # requirement
|
|
$self->{'alias'}, # alias
|
|
));
|
|
my $key = $dbh->bz_last_key( 'test_cases', 'case_id' );
|
|
|
|
$self->store_text($key, $self->{'author_id'}, $self->{'action'}, $self->{'effect'},
|
|
$self->{'setup'}, $self->{'breakdown'},0 ,$timestamp);
|
|
$self->update_deps($self->{'dependson'}, $self->{'blocks'}, $key);
|
|
foreach my $p (@{$self->{'plans'}}){
|
|
$self->link_plan($p->id, $key);
|
|
}
|
|
return $key;
|
|
}
|
|
|
|
=head2 store_text
|
|
|
|
Stores the test case document (action and effect) in the test_case_texts
|
|
table. Used by both store and copy. Accepts the the test case id,
|
|
author id, action text, effect text, and an optional timestamp.
|
|
|
|
=cut
|
|
|
|
sub store_text {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($key, $author, $action, $effect, $setup, $breakdown, $reset_version, $timestamp) = @_;
|
|
if (!defined $timestamp){
|
|
($timestamp) = Bugzilla::Extension::Testopia::Util::get_time_stamp();
|
|
}
|
|
trick_taint($action) if $action;
|
|
trick_taint($effect) if $effect;
|
|
trick_taint($breakdown) if $breakdown;
|
|
trick_taint($setup) if $setup;
|
|
detaint_natural($key);
|
|
|
|
my $version = $reset_version ? 0 : $self->version || 0;
|
|
$dbh->do("INSERT INTO test_case_texts
|
|
(case_id, case_text_version, who, creation_ts, action, effect, setup, breakdown)
|
|
VALUES(?,?,?,?,?,?,?,?)",
|
|
undef, $key, ++$version, $author,
|
|
$timestamp, $action, $effect, $setup, $breakdown);
|
|
$self->{'version'} = $version;
|
|
return $self->{'version'};
|
|
|
|
}
|
|
|
|
=head2 link_plan
|
|
|
|
Creates a link to the specified plan id. Optionally can create a link
|
|
for an arbitrary test case, not just this one.
|
|
|
|
=cut
|
|
|
|
sub link_plan {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($plan_id, $case_id) = @_;
|
|
$case_id = $self->{'case_id'} unless defined $case_id;
|
|
|
|
#Check that it isn't linked already
|
|
|
|
$dbh->bz_start_transaction();
|
|
my ($is) = $dbh->selectrow_array(
|
|
"SELECT 1
|
|
FROM test_case_plans
|
|
WHERE case_id = ?
|
|
AND plan_id = ?",
|
|
undef, ($case_id, $plan_id));
|
|
if ($is) {
|
|
$dbh->bz_commit_transaction();
|
|
return;
|
|
}
|
|
$dbh->do("INSERT INTO test_case_plans (plan_id, case_id)
|
|
VALUES (?,?)", undef, $plan_id, $case_id);
|
|
$dbh->bz_commit_transaction();
|
|
|
|
# Update the plans array to include new plan added.
|
|
|
|
push @{$self->{'plans'}}, Bugzilla::Extension::Testopia::TestPlan->new($plan_id);
|
|
|
|
}
|
|
|
|
|
|
=head2 unlink_plan
|
|
|
|
Removes the link to the specified plan id.
|
|
|
|
=cut
|
|
|
|
sub unlink_plan {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($plan_id) = @_;
|
|
|
|
detaint_natural($plan_id);
|
|
|
|
my $plan = Bugzilla::Extension::Testopia::TestPlan->new($plan_id);
|
|
|
|
if (scalar @{$self->plans} == 1){
|
|
$self->obliterate;
|
|
}
|
|
else {
|
|
$dbh->bz_start_transaction();
|
|
|
|
foreach my $run (@{$plan->test_runs}){
|
|
$dbh->do("DELETE FROM test_case_runs
|
|
WHERE case_id = ?
|
|
AND run_id = ?", undef, $self->id, $run->id);
|
|
}
|
|
|
|
$dbh->do("DELETE FROM test_case_plans
|
|
WHERE plan_id = ?
|
|
AND case_id = ?",
|
|
undef, $plan_id, $self->{'case_id'});
|
|
|
|
$dbh->bz_commit_transaction();
|
|
}
|
|
# Update the plans array.
|
|
delete $self->{'plans'};
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 copy
|
|
|
|
Creates a copy of this test case. Accepts the plan id to link to and
|
|
a boolean representing whether to copy the case document as well.
|
|
|
|
=cut
|
|
|
|
sub copy {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($author, $tester, $copydoc, $category_id) = @_;
|
|
# Exclude the auto-incremented field from the column list.
|
|
my $columns = join(", ", grep {$_ ne 'case_id'} DB_COLUMNS);
|
|
my ($timestamp) = Bugzilla::Extension::Testopia::Util::get_time_stamp();
|
|
$category_id ||= $self->{'category_id'};
|
|
|
|
$dbh->do("INSERT INTO test_cases ($columns) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
undef,
|
|
($self->{'case_status_id'}, # case_status_id
|
|
$category_id, # category_id
|
|
$self->{'priority_id'}, # priority_id
|
|
$author, # author_id
|
|
$tester, # default_tester_id
|
|
$timestamp, # creation_date
|
|
$self->{'estimated_time'}, # estimated_time
|
|
$self->{'isautomated'}, # isautomated
|
|
$self->sortkey, # sortkey
|
|
$self->{'script'}, # script
|
|
$self->{'arguments'}, # arguments
|
|
$self->{'summary'}, # summary
|
|
$self->{'requirement'}, # requirement
|
|
undef # alias
|
|
));
|
|
|
|
my $key = $dbh->bz_last_key( 'test_cases', 'case_id' );
|
|
|
|
if ($copydoc){
|
|
$self->store_text($key, Bugzilla->user->id, $self->text->{'action'},
|
|
$self->text->{'effect'}, $self->text->{'setup'},
|
|
$self->text->{'breakdown'},'VRESET' , $timestamp);
|
|
}
|
|
else{
|
|
$self->store_text($key, Bugzilla->user->id, '', '', '', '', 'VRESET', $timestamp);
|
|
}
|
|
return $key;
|
|
|
|
}
|
|
|
|
=head2 check_alias
|
|
|
|
Checks if the given alias exists already. Returns the case_id of
|
|
the matching case if it does.
|
|
|
|
=cut
|
|
|
|
|
|
|
|
=head2 class_check_alias
|
|
|
|
Checks if the given alias exists already. Returns the case_id of
|
|
the matching test case if it does.
|
|
|
|
=cut
|
|
|
|
sub class_check_alias {
|
|
my ($alias) = @_;
|
|
|
|
return unless $alias;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($id) = $dbh->selectrow_array(
|
|
"SELECT case_id
|
|
FROM test_cases
|
|
WHERE alias = ?",
|
|
undef, ($alias));
|
|
|
|
return $id;
|
|
}
|
|
|
|
=head2 update
|
|
|
|
Updates this test case with new values supplied by the user.
|
|
Accepts a reference to a hash with keys identical to a test cases
|
|
fields and values representing the new values entered.
|
|
Validation tests should be performed on the values
|
|
before calling this method. If a field is changed, a history
|
|
of that change is logged in the test_case_activity table.
|
|
|
|
=cut
|
|
|
|
|
|
=head2 history
|
|
|
|
Returns a reference to a list of history entries from the
|
|
test_case_activity table.
|
|
|
|
=cut
|
|
|
|
sub history {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $ref = $dbh->selectall_arrayref(
|
|
"SELECT defs.description AS what,
|
|
p.login_name AS who, a.changed, a.oldvalue, a.newvalue
|
|
FROM test_case_activity AS a
|
|
JOIN test_fielddefs AS defs ON a.fieldid = defs.fieldid
|
|
JOIN profiles AS p ON a.who = p.userid
|
|
WHERE a.case_id = ?",
|
|
{'Slice'=>{}}, $self->{'case_id'});
|
|
|
|
foreach my $row (@$ref){
|
|
if ($row->{'what'} eq 'Case Status'){
|
|
$row->{'oldvalue'} = lookup_status($row->{'oldvalue'});
|
|
$row->{'newvalue'} = lookup_status($row->{'newvalue'});
|
|
}
|
|
elsif ($row->{'what'} eq 'Category'){
|
|
$row->{'oldvalue'} = lookup_category($row->{'oldvalue'});
|
|
$row->{'newvalue'} = lookup_category($row->{'newvalue'});
|
|
}
|
|
elsif ($row->{'what'} eq 'Priority'){
|
|
$row->{'oldvalue'} = lookup_priority($row->{'oldvalue'});
|
|
$row->{'newvalue'} = lookup_priority($row->{'newvalue'});
|
|
}
|
|
elsif ($row->{'what'} eq 'Default Tester'){
|
|
$row->{'oldvalue'} = lookup_default_tester($row->{'oldvalue'});
|
|
$row->{'newvalue'} = lookup_default_tester($row->{'newvalue'});
|
|
}
|
|
$row->{'changed'} = format_time($row->{'changed'}, TIME_FORMAT);
|
|
}
|
|
|
|
return $ref;
|
|
}
|
|
|
|
sub last_changed {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my ($date) = $dbh->selectrow_array(
|
|
"SELECT MAX(changed)
|
|
FROM test_case_activity
|
|
WHERE case_id = ?",
|
|
undef, $self->id);
|
|
|
|
return $self->{'creation_date'} unless $date;
|
|
return $date;
|
|
|
|
}
|
|
|
|
|
|
# From process bug
|
|
sub _snap_shot_deps {
|
|
my ($i, $target, $me) = (@_);
|
|
my $dbh = Bugzilla->dbh;
|
|
my $ref = $dbh->selectcol_arrayref(
|
|
"SELECT $target
|
|
FROM test_case_dependencies
|
|
WHERE $me = ? ORDER BY $target", undef, $i);
|
|
|
|
return join(',', @{$ref});
|
|
}
|
|
|
|
# Taken from Bugzilla::Bug
|
|
sub _get_dep_lists {
|
|
my ($myfield, $targetfield, $case_id) = (@_);
|
|
my $dbh = Bugzilla->dbh;
|
|
my $list_ref =
|
|
$dbh->selectcol_arrayref(
|
|
"SELECT test_case_dependencies.$targetfield
|
|
FROM test_case_dependencies
|
|
JOIN test_cases ON test_cases.case_id = test_case_dependencies.$targetfield
|
|
WHERE test_case_dependencies.$myfield = ?
|
|
ORDER BY test_case_dependencies.$targetfield",
|
|
undef, ($case_id));
|
|
return $list_ref;
|
|
}
|
|
|
|
sub update_deps {
|
|
my $self = shift;
|
|
my ($dependson, $blocks, $case_id) = @_;
|
|
$case_id = $self->{'case_id'} unless $case_id;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $fields = {};
|
|
$fields->{'dependson'} = $dependson;
|
|
$fields->{'blocked'} = $blocks;
|
|
# From process bug
|
|
foreach my $field ("dependson", "blocked") {
|
|
if (exists $fields->{$field}) {
|
|
my @validvalues;
|
|
foreach my $id (split(/[\s,]+/, $fields->{$field})) {
|
|
next unless $id;
|
|
Bugzilla::Extension::Testopia::Util::validate_test_id($id, 'case');
|
|
push(@validvalues, $id);
|
|
}
|
|
$fields->{$field} = join(",", @validvalues);
|
|
}
|
|
}
|
|
#From Bug.pm sub ValidateDependencies($$$)
|
|
my $id = $case_id || 0;
|
|
|
|
unless (defined($fields->{'dependson'})
|
|
|| defined($fields->{'blocked'}))
|
|
{
|
|
return;
|
|
}
|
|
|
|
my %deps;
|
|
my %deptree;
|
|
foreach my $pair (["blocked", "dependson"], ["dependson", "blocked"]) {
|
|
my ($me, $target) = @{$pair};
|
|
$deptree{$target} = [];
|
|
$deps{$target} = [];
|
|
next unless $fields->{$target};
|
|
|
|
my %seen;
|
|
foreach my $i (split('[\s,]+', $fields->{$target})) {
|
|
if ($id == $i) {
|
|
ThrowUserError("dependency_loop_single");
|
|
}
|
|
if (!exists $seen{$i}) {
|
|
push(@{$deptree{$target}}, $i);
|
|
$seen{$i} = 1;
|
|
}
|
|
}
|
|
# populate $deps{$target} as first-level deps only.
|
|
# and find remainder of dependency tree in $deptree{$target}
|
|
@{$deps{$target}} = @{$deptree{$target}};
|
|
my @stack = @{$deps{$target}};
|
|
while (@stack) {
|
|
my $i = shift @stack;
|
|
my $dep_list =
|
|
$dbh->selectcol_arrayref("SELECT $target
|
|
FROM test_case_dependencies
|
|
WHERE $me = ?", undef, $i);
|
|
foreach my $t (@$dep_list) {
|
|
# ignore any _current_ dependencies involving this test_case,
|
|
# as they will be overwritten with data from the form.
|
|
if ($t != $id && !exists $seen{$t}) {
|
|
push(@{$deptree{$target}}, $t);
|
|
push @stack, $t;
|
|
$seen{$t} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
my @deps = @{$deptree{'dependson'}};
|
|
my @blocks = @{$deptree{'blocked'}};
|
|
my @union = ();
|
|
my @isect = ();
|
|
my %union = ();
|
|
my %isect = ();
|
|
foreach my $block (@deps, @blocks) { $union{$block}++ && $isect{$block}++ }
|
|
@union = keys %union;
|
|
@isect = keys %isect;
|
|
if (scalar(@isect) > 0) {
|
|
my $both = "";
|
|
foreach my $i (@isect) {
|
|
$both .= "<a href=\"tr_show_case.cgi?case_id=$i\">$i</a> " ;
|
|
}
|
|
ThrowUserError("dependency_loop_multi", { both => $both });
|
|
}
|
|
|
|
#from process_bug
|
|
foreach my $pair ("blocked/dependson", "dependson/blocked") {
|
|
my ($me, $target) = split("/", $pair);
|
|
|
|
my @oldlist = @{$dbh->selectcol_arrayref("SELECT $target FROM test_case_dependencies
|
|
WHERE $me = ? ORDER BY $target",
|
|
undef, $id)};
|
|
|
|
if (defined $fields->{$target}) {
|
|
my %snapshot;
|
|
my @newlist = sort {$a <=> $b} @{$deps{$target}};
|
|
|
|
while (0 < @oldlist || 0 < @newlist) {
|
|
if (@oldlist == 0 || (@newlist > 0 &&
|
|
$oldlist[0] > $newlist[0])) {
|
|
$snapshot{$newlist[0]} = _snap_shot_deps($newlist[0], $me,
|
|
$target);
|
|
shift @newlist;
|
|
} elsif (@newlist == 0 || (@oldlist > 0 &&
|
|
$newlist[0] > $oldlist[0])) {
|
|
$snapshot{$oldlist[0]} = _snap_shot_deps($oldlist[0], $me,
|
|
$target);
|
|
shift @oldlist;
|
|
} else {
|
|
if ($oldlist[0] != $newlist[0]) {
|
|
die "Error in list comparing code";
|
|
}
|
|
shift @oldlist;
|
|
shift @newlist;
|
|
}
|
|
}
|
|
my @keys = keys(%snapshot);
|
|
if (@keys) {
|
|
my $oldsnap = _snap_shot_deps($id, $target, $me);
|
|
$dbh->do("DELETE FROM test_case_dependencies WHERE $me = ?", undef, $id);
|
|
foreach my $i (@{$deps{$target}}) {
|
|
$dbh->do("INSERT INTO test_case_dependencies ($me, $target) VALUES (?,?)", undef, $id, $i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete $self->{'blocked'};
|
|
delete $self->{'blocks'};
|
|
delete $self->{'dependson'};
|
|
delete $self->{'blocked_list'};
|
|
delete $self->{'dependson_list'};
|
|
}
|
|
|
|
=head2 get_dep_tree
|
|
|
|
Returns a list of test case dependencies
|
|
|
|
=cut
|
|
|
|
sub get_dep_tree {
|
|
my $self = shift;
|
|
$self->{'dep_list'} = ();
|
|
$self->_generate_dep_tree($self->{'case_id'});
|
|
return $self->{'dep_list'};
|
|
}
|
|
|
|
=head2 _generate_dep_tree
|
|
|
|
Private method that recursivly gets a list of the test cases this blocks
|
|
|
|
=cut
|
|
|
|
sub _generate_dep_tree {
|
|
my $self = shift;
|
|
my ($case_id) = @_;
|
|
my $deps = _get_dep_lists("dependson", "blocked", $case_id);
|
|
return unless scalar @$deps;
|
|
foreach my $id (@$deps){
|
|
$self->_generate_dep_tree($id);
|
|
push @{$self->{'dep_list'}}, $id
|
|
}
|
|
}
|
|
|
|
=head2 obliterate
|
|
|
|
Removes this case and all things that reference it.
|
|
|
|
=cut
|
|
|
|
sub obliterate {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
foreach my $obj (@{$self->attachments}){
|
|
$obj->unlink_case($self->id);
|
|
}
|
|
foreach my $obj (@{$self->caseruns}){
|
|
$obj->obliterate;
|
|
}
|
|
|
|
$dbh->do("DELETE FROM test_case_texts WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_plans WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_components WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_tags WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_bugs WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_activity WHERE case_id = ?", undef, $self->id);
|
|
$dbh->do("DELETE FROM test_case_dependencies
|
|
WHERE dependson = ? OR blocked = ?", undef, ($self->id, $self->id));
|
|
$dbh->do("DELETE FROM test_cases WHERE case_id = ?", undef, $self->id);
|
|
return 1;
|
|
}
|
|
|
|
sub fields {
|
|
my @fields = qw(
|
|
product
|
|
plans
|
|
summary
|
|
author_id
|
|
default_tester_id
|
|
case_status_id
|
|
priority_id
|
|
category_id
|
|
components
|
|
requirement
|
|
estimated_time
|
|
isautomated
|
|
script
|
|
arguments
|
|
alias
|
|
tags
|
|
bugs
|
|
depends_on
|
|
blocks
|
|
runs
|
|
set_up
|
|
break_down
|
|
action
|
|
expected_results
|
|
creation_date
|
|
version
|
|
case_id
|
|
);
|
|
return \@fields;
|
|
|
|
}
|
|
|
|
sub TO_JSON {
|
|
my $self = shift;
|
|
my $obj;
|
|
my $json = new JSON;
|
|
|
|
|
|
my @plan_ids;
|
|
my @comps;
|
|
foreach my $p (@{$self->plans}){
|
|
push @plan_ids, $p->id;
|
|
}
|
|
foreach my $field ($self->DB_COLUMNS){
|
|
$obj->{$field} = $self->{$field};
|
|
}
|
|
foreach my $c (@{$self->components}){
|
|
push @comps, $c->name;
|
|
}
|
|
|
|
$obj->{'plan_name'} = ${$self->plans}[0]->name;
|
|
$obj->{'run_count'} = $self->run_count;
|
|
$obj->{'author_name'} = $self->author->name if $self->author;
|
|
$obj->{'default_tester'} = $self->default_tester->name if $self->default_tester;
|
|
$obj->{'status'} = $self->status;
|
|
$obj->{'priority'} = $self->priority;
|
|
$obj->{'plan_id'} = $plan_ids[0];
|
|
$obj->{'plan_ids'} = \@plan_ids;
|
|
$obj->{'type'} = $self->type;
|
|
$obj->{'id'} = $self->id;
|
|
$obj->{'canedit'} = $self->canedit;
|
|
$obj->{'canview'} = $self->canview;
|
|
$obj->{'candelete'} = $self->candelete;
|
|
$obj->{'category_name'} = $self->category->name if $self->category;
|
|
$obj->{'product_id'} = $self->plans->[0]->product_id if scalar @{$self->plans};
|
|
$obj->{'blocked'} = $self->blocked_list;
|
|
$obj->{'dependson'} = $self->dependson_list;
|
|
$obj->{'component'} = join(',',@comps);
|
|
$obj->{'modified'} = format_time($self->last_changed, TIME_FORMAT);
|
|
$obj->{'creation_date'} = format_time($self->{'creation_date'}, TIME_FORMAT);
|
|
$obj->{'average_time'} = $self->calculate_average_time;
|
|
|
|
|
|
return $json->encode($obj);
|
|
}
|
|
|
|
=head2 canview
|
|
|
|
Returns true if the logged in user has rights to view this test case.
|
|
|
|
=cut
|
|
|
|
sub canview {
|
|
my $self = shift;
|
|
return 1 if Bugzilla->user->in_group('Testers');
|
|
return 1 if $self->get_user_rights(Bugzilla->user->id) & TR_READ;
|
|
return 0;
|
|
}
|
|
|
|
=head2 canedit
|
|
|
|
Returns true if the logged in user has rights to edit this test case.
|
|
|
|
=cut
|
|
|
|
sub canedit {
|
|
my $self = shift;
|
|
return 1 if Bugzilla->user->in_group('Testers');
|
|
return 1 if $self->get_user_rights(Bugzilla->user->id) & TR_WRITE;
|
|
return 0;
|
|
}
|
|
|
|
=head2 candelete
|
|
|
|
Returns true if the logged in user has rights to delete this test case.
|
|
|
|
=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"};
|
|
# Otherwise, check for delete rights on all the plans this is linked to
|
|
my $own_all = 1;
|
|
foreach my $plan (@{$self->plans}){
|
|
if (!($plan->get_user_rights(Bugzilla->user->id) & TR_DELETE)) {
|
|
$own_all = 0;
|
|
last;
|
|
}
|
|
}
|
|
return 1 if $own_all;
|
|
return 0;
|
|
}
|
|
|
|
=head2 can_unlink_plan
|
|
|
|
Returns true if this test case can be unlinked from the given plan
|
|
|
|
=cut
|
|
|
|
sub can_unlink_plan {
|
|
my $self = shift;
|
|
my ($plan_id) = @_;
|
|
|
|
my $plan = Bugzilla::Extension::Testopia::TestPlan->new($plan_id);
|
|
return 1 if Bugzilla->user->in_group('admin');
|
|
return 1 if Bugzilla->user->in_group('Testers') && Bugzilla->params->{"testopia-allow-group-member-deletes"};
|
|
return 1 if $plan->get_user_rights(Bugzilla->user->id) & TR_DELETE;
|
|
return 0;
|
|
}
|
|
|
|
sub get_user_rights {
|
|
my $self = shift;
|
|
my ($userid) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $plan_ids = $dbh->selectcol_arrayref(
|
|
"SELECT plan_id FROM test_case_plans WHERE case_id = ?",
|
|
undef, $self->id);
|
|
|
|
return 0 unless $plan_ids;
|
|
$plan_ids = join(',',@$plan_ids);
|
|
|
|
my ($perms) = $dbh->selectrow_array(
|
|
"SELECT MAX(permissions) FROM test_plan_permissions
|
|
LEFT JOIN test_case_plans ON test_plan_permissions.plan_id = test_case_plans.plan_id
|
|
INNER JOIN test_cases ON test_case_plans.case_id = test_cases.case_id
|
|
WHERE userid = ? AND test_plan_permissions.plan_id IN ($plan_ids)",
|
|
undef, $userid);
|
|
|
|
return $perms;
|
|
}
|
|
###############################
|
|
#### Accessors ####
|
|
###############################
|
|
|
|
=head1 ACCESSOR METHODS
|
|
|
|
=head2 id
|
|
|
|
Returns the ID of this object
|
|
|
|
=head2 author
|
|
|
|
Returns a Bugzilla::User object representing the Author of this case
|
|
|
|
=head2 default_tester
|
|
|
|
Returns a Bugzilla::User object representing the run's default tester
|
|
|
|
=head2 creation_date
|
|
|
|
Returns the creation time stamp of this object
|
|
|
|
=head2 isautomated
|
|
|
|
Returns true if this is an automatic test case
|
|
|
|
=head2 script
|
|
|
|
Returns the script of this object
|
|
|
|
=head2 status_id
|
|
|
|
Returns the status_id of this object
|
|
|
|
=head2 arguments
|
|
|
|
Returns the arguments for the script of this object
|
|
|
|
=head2 summary
|
|
|
|
Returns the summary of this object
|
|
|
|
=head2 requirements
|
|
|
|
Returns the requirements of this object
|
|
|
|
=head2 alias
|
|
|
|
Returns the alias of this object
|
|
|
|
=cut
|
|
|
|
sub id { return $_[0]->{'case_id'}; }
|
|
sub author { return Bugzilla::User->new($_[0]->{'author_id'}); }
|
|
sub default_tester { return Bugzilla::User->new($_[0]->{'default_tester_id'}); }
|
|
sub creation_date { return $_[0]->{'creation_date'}; }
|
|
sub estimated_time { return $_[0]->{'estimated_time'}; }
|
|
sub isautomated { return $_[0]->{'isautomated'}; }
|
|
sub script { return $_[0]->{'script'}; }
|
|
sub status_id { return $_[0]->{'case_status_id'};}
|
|
sub arguments { return $_[0]->{'arguments'}; }
|
|
sub summary { return $_[0]->{'summary'}; }
|
|
sub requirement { return $_[0]->{'requirement'}; }
|
|
sub alias { return $_[0]->{'alias'}; }
|
|
|
|
=head2 type
|
|
|
|
Returns 'case'
|
|
|
|
=cut
|
|
|
|
sub type {
|
|
my $self = shift;
|
|
$self->{'type'} = 'case';
|
|
return $self->{'type'};
|
|
}
|
|
|
|
=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->selectcol_arrayref(
|
|
"SELECT attachment_id
|
|
FROM test_case_attachments
|
|
WHERE case_id = ?",
|
|
undef, $self->{'case_id'});
|
|
|
|
my @attachments;
|
|
foreach my $attach (@{$attachments}){
|
|
push @attachments, Bugzilla::Extension::Testopia::Attachment->new($attach);
|
|
}
|
|
$self->{'attachments'} = \@attachments;
|
|
return $self->{'attachments'};
|
|
|
|
}
|
|
|
|
=head2 version
|
|
|
|
Returns the case text version. This number is incremented any time
|
|
changes are made to the case docs (action and effect).
|
|
|
|
=cut
|
|
|
|
sub version {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'version'} if exists $self->{'version'};
|
|
my ($ver) = $dbh->selectrow_array("SELECT MAX(case_text_version)
|
|
FROM test_case_texts
|
|
WHERE case_id = ?",
|
|
undef, $self->{'case_id'});
|
|
$self->{'version'} = $ver;
|
|
return $self->{'version'};
|
|
}
|
|
|
|
=head2 status
|
|
|
|
Looks up the case status based on the case_status_id of this case.
|
|
|
|
=cut
|
|
|
|
sub status {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'status'} if exists $self->{'status'};
|
|
my ($res) = $dbh->selectrow_array("SELECT name
|
|
FROM test_case_status
|
|
WHERE case_status_id = ?",
|
|
undef, $self->{'case_status_id'});
|
|
$self->{'status'} = $res;
|
|
return $self->{'status'};
|
|
}
|
|
|
|
=head2 priority
|
|
|
|
Looks up the Bugzilla priority value based on the priority_id
|
|
of this case.
|
|
|
|
=cut
|
|
|
|
sub priority {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'priority'} if exists $self->{'priority'};
|
|
my ($res) = $dbh->selectrow_array("SELECT value
|
|
FROM priority
|
|
WHERE id = ?",
|
|
undef, $self->{'priority_id'});
|
|
$self->{'priority'} = $res;
|
|
return $self->{'priority'};
|
|
}
|
|
|
|
=head2 category
|
|
|
|
Returns the category name based on the category_id of this case
|
|
|
|
=cut
|
|
|
|
sub category {
|
|
my $self = shift;
|
|
return $self->{'category'} if exists $self->{'category'};
|
|
$self->{'category'} = Bugzilla::Extension::Testopia::Category->new($self->{'category_id'});
|
|
return $self->{'category'};
|
|
}
|
|
|
|
=head2 components
|
|
|
|
Returns a reference to a list of bugzilla components assoicated with
|
|
this test case.
|
|
|
|
=cut
|
|
|
|
sub components {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'components'} if exists $self->{'components'};
|
|
my $comps = $dbh->selectcol_arrayref(
|
|
"SELECT comp.id
|
|
FROM components AS comp
|
|
JOIN test_case_components AS tcc ON tcc.component_id = comp.id
|
|
JOIN test_cases ON tcc.case_id = test_cases.case_id
|
|
WHERE test_cases.case_id = ?",
|
|
{'Slice' => {}}, $self->{'case_id'});
|
|
|
|
my @comps;
|
|
foreach my $id (@$comps){
|
|
my $comp = Bugzilla::Component->new($id);
|
|
my $prod = Bugzilla::Product->new($comp->product_id);
|
|
$comp->{'product_name'} = $prod->name;
|
|
push @comps, $comp;
|
|
}
|
|
$self->{'components'} = \@comps;
|
|
return $self->{'components'};
|
|
}
|
|
|
|
=head2 tags
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestTag objects
|
|
associated with this case.
|
|
|
|
=cut
|
|
|
|
sub tags {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'tags'} if exists $self->{'tags'};
|
|
my $tagids = $dbh->selectcol_arrayref("SELECT test_case_tags.tag_id
|
|
FROM test_case_tags
|
|
INNER JOIN test_tags ON test_case_tags.tag_id = test_tags.tag_id
|
|
WHERE case_id = ?
|
|
ORDER BY test_tags.tag_name",
|
|
undef, $self->{'case_id'});
|
|
my @tags;
|
|
foreach my $id (@{$tagids}){
|
|
push @tags, Bugzilla::Extension::Testopia::TestTag->new($id);
|
|
}
|
|
$self->{'tags'} = \@tags;
|
|
return $self->{'tags'};
|
|
}
|
|
|
|
=head2 plans
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestPlan objects
|
|
associated with this case.
|
|
|
|
=cut
|
|
|
|
sub plans {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'plans'} if exists $self->{'plans'};
|
|
my $ref = $dbh->selectcol_arrayref("SELECT plan_id
|
|
FROM test_case_plans
|
|
WHERE case_id = ? ORDER BY plan_id",
|
|
undef, $self->{'case_id'});
|
|
my @plans;
|
|
foreach my $id (@{$ref}){
|
|
push @plans, Bugzilla::Extension::Testopia::TestPlan->new($id);
|
|
}
|
|
$self->{'plans'} = \@plans;
|
|
return $self->{'plans'};
|
|
}
|
|
|
|
sub plan_list {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'plan_list'} if exists $self->{'plan_list'};
|
|
my $ref = $dbh->selectcol_arrayref("SELECT plan_id
|
|
FROM test_case_plans
|
|
WHERE case_id = ? ORDER BY plan_id",
|
|
undef, $self->{'case_id'});
|
|
$self->{'plan_list'} = join(',', @$ref);
|
|
return $self->{'plan_list'};
|
|
}
|
|
|
|
=head2 bugs
|
|
|
|
Returns a reference to a list of Bugzilla::Bug objects
|
|
associated with this case.
|
|
|
|
=cut
|
|
|
|
sub bugs {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'bugs'} if exists $self->{'bugs'};
|
|
my $ref = $dbh->selectall_arrayref(
|
|
"SELECT bug_id, case_run_id
|
|
FROM test_case_bugs
|
|
WHERE case_id = ?",
|
|
{'Slice' => {}}, $self->{'case_id'});
|
|
my @bugs;
|
|
foreach my $row (@{$ref}){
|
|
next unless Bugzilla->user->can_see_bug($row->{'bug_id'});
|
|
my $bug = Bugzilla::Bug->new($row->{'bug_id'}, Bugzilla->user->id);
|
|
if ($row->{'case_run_id'}){
|
|
my $cr = Bugzilla::Extension::Testopia::TestCaseRun->new($row->{'case_run_id'});
|
|
next unless $cr;
|
|
$bug->{'build'} = $cr->build->name;
|
|
$bug->{'env'} = $cr->environment->name;
|
|
$bug->{'run_id'} = $cr->run_id;
|
|
$bug->{'case_run_id'} = $cr->id;
|
|
}
|
|
push @bugs, $bug;
|
|
}
|
|
$self->{'bugs'} = \@bugs;
|
|
return $self->{'bugs'};
|
|
}
|
|
|
|
=head2 bug_list
|
|
|
|
Returns a comma separated list of bug ids associated with this case
|
|
|
|
=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 bug_id
|
|
FROM test_case_bugs
|
|
WHERE case_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 text
|
|
|
|
Returns a hash reference representing the action and effect of this
|
|
case.
|
|
|
|
=cut
|
|
|
|
|
|
sub text {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($version) = @_;
|
|
trick_taint($version) if $version;
|
|
return $self->{'text'} if exists $self->{'text'} && !$version;
|
|
|
|
$version = $version || $self->version;
|
|
|
|
my $text = $dbh->selectrow_hashref(
|
|
"SELECT action, effect, setup, breakdown, profiles.realname AS author, case_text_version AS version
|
|
FROM test_case_texts
|
|
INNER JOIN profiles on profiles.userid = test_case_texts.who
|
|
WHERE case_id = ? AND case_text_version = ?",
|
|
undef, ($self->{'case_id'}, $version));
|
|
|
|
return $text if scalar @_;
|
|
|
|
$self->{'text'} = $text;
|
|
return $self->{'text'};
|
|
}
|
|
|
|
=head2 runs
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestRun objects
|
|
associated with this case.
|
|
|
|
=cut
|
|
|
|
sub runs {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'runs'} if exists $self->{'runs'};
|
|
my $ref = $dbh->selectcol_arrayref(
|
|
"SELECT DISTINCT t.run_id
|
|
FROM test_runs t
|
|
INNER JOIN test_case_runs r ON r.run_id = t.run_id
|
|
WHERE case_id = ?",
|
|
undef, $self->{'case_id'});
|
|
my @runs;
|
|
foreach my $id (@{$ref}){
|
|
push @runs, Bugzilla::Extension::Testopia::TestRun->new($id);
|
|
}
|
|
$self->{'runs'} = \@runs;
|
|
return $self->{'runs'};
|
|
}
|
|
|
|
sub run_count {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($runcount) = $dbh->selectrow_array(
|
|
"SELECT DISTINCT count(run_id)
|
|
FROM test_case_runs
|
|
WHERE case_id = ?",
|
|
undef, $self->id);
|
|
return $runcount;
|
|
}
|
|
|
|
=head2 caseruns
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestCaseRun objects
|
|
associated with this case.
|
|
|
|
=cut
|
|
|
|
sub caseruns {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'caseruns'} if exists $self->{'caseruns'};
|
|
my $ref = $dbh->selectcol_arrayref("SELECT case_run_id
|
|
FROM test_case_runs
|
|
WHERE case_id = ?",
|
|
undef, $self->{'case_id'});
|
|
my @runs;
|
|
foreach my $id (@{$ref}){
|
|
push @runs, Bugzilla::Extension::Testopia::TestCaseRun->new($id);
|
|
}
|
|
$self->{'caseruns'} = \@runs;
|
|
return $self->{'caseruns'};
|
|
}
|
|
|
|
sub sortkey {
|
|
my $self = shift;
|
|
return $self->{'sortkey'} if exists $self->{'sortkey'};
|
|
my $dbh = Bugzilla->dbh;
|
|
my ($sortkey) = $dbh->selectrow_array("SELECT MAX(sortkey) FROM test_cases");
|
|
$self->{'sortkey'} = ++$sortkey;
|
|
return $self->{'sortkey'};
|
|
}
|
|
|
|
=head2 blocked
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestCase objects
|
|
which are blocked by this test case.
|
|
|
|
=cut
|
|
|
|
sub blocked {
|
|
my ($self) = @_;
|
|
return $self->{'blocked'} if exists $self->{'blocked'};
|
|
my @deps;
|
|
my $ref = _get_dep_lists("dependson", "blocked", $self->{'case_id'});
|
|
foreach my $id (@{$ref}){
|
|
push @deps, Bugzilla::Extension::Testopia::TestCase->new($id);
|
|
}
|
|
$self->{'blocked'} = \@deps;
|
|
return $self->{'blocked'};
|
|
}
|
|
|
|
sub blocked_list {
|
|
my ($self) = @_;
|
|
return $self->{'blocked_list'} if exists $self->{'blocked_list'};
|
|
my @deps;
|
|
my $ref = _get_dep_lists("dependson", "blocked", $self->{'case_id'});
|
|
$self->{'blocked_list'} = join(",", @$ref);
|
|
return $self->{'blocked_list'};
|
|
}
|
|
|
|
=head2 blocked_list_uncached
|
|
|
|
Returns a space separated list of test cases that are blocked by this test case.
|
|
This method does not cache the blocked test cases so each call will result
|
|
in a database read.
|
|
|
|
=cut
|
|
|
|
sub blocked_list_uncached {
|
|
my ($self) = @_;
|
|
my $ref = _get_dep_lists("dependson", "blocked", $self->{'case_id'});
|
|
return join(" ", @$ref)
|
|
}
|
|
|
|
=head2 dependson
|
|
|
|
Returns a reference to a list of Bugzilla::Extension::Testopia::TestCase objects
|
|
which depend on this test case.
|
|
|
|
=cut
|
|
|
|
sub dependson {
|
|
my ($self) = @_;
|
|
return $self->{'dependson'} if exists $self->{'dependson'};
|
|
my @deps;
|
|
my $ref = _get_dep_lists("blocked", "dependson", $self->{'case_id'});
|
|
foreach my $id (@{$ref}){
|
|
push @deps, Bugzilla::Extension::Testopia::TestCase->new($id);
|
|
}
|
|
$self->{'dependson'} = \@deps;
|
|
return $self->{'dependson'};
|
|
}
|
|
|
|
sub dependson_list {
|
|
my ($self) = @_;
|
|
return $self->{'dependson_list'} if exists $self->{'dependson_list'};
|
|
my @deps;
|
|
my $ref = _get_dep_lists("blocked", "dependson", $self->{'case_id'});
|
|
$self->{'dependson_list'} = join(",", @$ref);
|
|
return $self->{'dependson_list'};
|
|
}
|
|
|
|
=head2 dependson_list_uncached
|
|
|
|
Returns a space separated list of test cases that depend on this test case.
|
|
This method does not cache the dependent test cases so each call will result
|
|
in a database read.
|
|
|
|
=cut
|
|
|
|
sub dependson_list_uncached {
|
|
my ($self) = @_;
|
|
my $ref = _get_dep_lists("blocked", "dependson", $self->{'case_id'});
|
|
return join(" ", @$ref)
|
|
}
|
|
|
|
sub calculate_average_time {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $totalseconds;
|
|
my $i = 0;
|
|
foreach my $cr (@{$self->caseruns}){
|
|
if ($cr->completion_time){
|
|
$totalseconds += $cr->completion_time;
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
my $average = $i ? int($totalseconds / $i) : 0;
|
|
|
|
my @time = gmtime($average);
|
|
my %time;
|
|
|
|
$time{hr} = $time[2];
|
|
$time{min} = $time[1];
|
|
$time{sec} = $time[0];
|
|
|
|
return $time{hr}.":".$time{min}.":".$time{sec};
|
|
}
|
|
|
|
|
|
=head1 TODO
|
|
|
|
=head1 SEE ALSO
|
|
|
|
TestPlan, TestRun, TestCaseRun
|
|
|
|
=head1 AUTHOR
|
|
|
|
Greg Hendricks <ghendricks@novell.com>
|
|
|
|
=cut
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Extension::Testopia::TestCase - Testopia Test Case object
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module represents a test case in Testopia. Each test case must
|
|
be linked to one or more test plans.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Bugzilla::Extension::Testopia::TestCase;
|
|
|
|
$case = Bugzilla::Extension::Testopia::TestCase->new($case_id);
|
|
$case = Bugzilla::Extension::Testopia::TestCase->new(\%case_hash);
|
|
|
|
=cut
|
|
|
|
=head1 FIELDS
|
|
|
|
case_id
|
|
case_status_id
|
|
category_id
|
|
priority_id
|
|
author_id
|
|
default_tester_id
|
|
creation_date
|
|
estimated_time
|
|
isautomated
|
|
sortkey
|
|
script
|
|
arguments
|
|
summary
|
|
requirement
|
|
alias
|
|
|
|
=cut
|
|
|
|
=head1 METHODS
|
|
|
|
=cut
|
|
=head2 lookup_status
|
|
|
|
Takes an ID of the status field and returns the value
|
|
|
|
=cut
|
|
=head2 lookup_status_by_name
|
|
|
|
Returns the id of the status name passed.
|
|
|
|
=cut
|
|
|
|
=head2 lookup_category
|
|
|
|
Takes an ID of the category field and returns the value
|
|
|
|
=cut
|
|
|
|
=head2 lookup_category_by_name
|
|
|
|
Returns the id of the category name passed.
|
|
|
|
=cut
|
|
|
|
=head2 lookup_priority
|
|
|
|
Takes an ID of the priority field and returns the value
|
|
|
|
=cut
|
|
|
|
=head2 lookup_priority_by_name
|
|
|
|
Returns the id of the priority name passed.
|
|
|
|
=cut
|
|
|
|
=head2 lookup_default_tester
|
|
|
|
Takes an ID of the default_tester field and returns the value
|
|
|
|
=cut
|
|
|