Comitting Testopia 1.1 beta

git-svn-id: svn://10.0.0.236/trunk@213182 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
ghendricks%novell.com
2006-10-05 21:11:04 +00:00
parent 7d531e2b9b
commit d0971aeeff
135 changed files with 10808 additions and 2481 deletions

View File

@@ -163,7 +163,7 @@ sub store {
$self->{'filename'}, $timestamp, $self->{'mime_type'});
my $key = $dbh->bz_last_key( 'test_attachments', 'attachment_id' );
$dbh->do("INSERT INTO test_attachment_data VALUES(?,?)",
$dbh->do("INSERT INTO test_attachment_data (attachment_id, contents) VALUES(?,?)",
undef, $key, $self->{'contents'});
return $key;

View File

@@ -0,0 +1,85 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
package Bugzilla::Testopia::Classification;
use strict;
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Config;
# Extends Bugzilla::Classification;
use base "Bugzilla::Classification";
use Bugzilla;
sub user_visible_products {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!$self->{'products'}) {
my $query = "SELECT id FROM products " .
"LEFT JOIN group_control_map " .
"ON group_control_map.product_id = products.id ";
if (Param('useentrygroupdefault')) {
$query .= "AND group_control_map.entry != 0 ";
} else {
$query .= "AND group_control_map.membercontrol = " .
CONTROLMAPMANDATORY . " ";
}
if (%{Bugzilla->user->groups}) {
$query .= "AND group_id NOT IN(" .
join(',', values(%{Bugzilla->user->groups})) . ") ";
}
$query .= "WHERE group_id IS NULL AND products.classification_id= ? ORDER BY products.name";
my $product_ids = $dbh->selectcol_arrayref($query, undef, $self->id);
my @products;
foreach my $product_id (@$product_ids) {
push (@products, new Bugzilla::Testopia::Product($product_id));
}
$self->{'user_visible_products'} = \@products;
}
return $self->{'user_visible_products'};
}
sub products_to_json {
my $self = shift;
my ($disable_move) = @_;
$disable_move ||= '';
$disable_move = ',"addChild","move"' if $disable_move;
my $products = $self->user_visible_products;
my $json = "[";
foreach my $obj (@{$products}){
$json .= '{title:"' . $obj->name . '",';
$json .= 'isFolder:' . (scalar @{$obj->environment_categories} > 0 ? "true" : "false") . ',';
$json .= 'objectId:"' . $obj->id . '",';
$json .= 'widgetId:"product' . $obj->id . '",';
$json .= 'actionsDisabled:["addElement","addProperty","addValue"'. $disable_move .'],';
$json .= 'childIconSrc:"testopia/img/folder_red.gif"},';
}
chop $json;
$json .= "]";
return $json;
}
1;

View File

@@ -16,7 +16,9 @@
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
# Michael Hight <mjhight@gmail.com>
# Garrett Braden <gbraden@novell.com>
=head1 NAME
@@ -26,11 +28,12 @@ Bugzilla::Testopia::Environment - A test environment
Environments are a set of parameters dictating what conditions
a test was conducted in. Each test run must have an environment.
Environments can be very simple or very complex. Since there is
no easy way of defining a limit for an environment, the bulk of
the information is captured as an XML document. At the very least
however, and environment consists of the OS and platform from
Bugzilla.
Environments can be very simple or very complex.
Environments are comprised of Elemements, Properties, and Values.
Elements can be nested within other elements. Each element can have
zero or more properties. Each property can only have one value selected
of the possible values.
=head1 SYNOPSIS
@@ -54,23 +57,23 @@ use Bugzilla::User;
=head1 FIELDS
environment_id
op_sys_id
rep_platform_id
product_id
name
xml
isactive
=cut
use constant DB_COLUMNS => qw(
test_environments.environment_id
test_environments.op_sys_id
test_environments.rep_platform_id
test_environments.name
test_environments.xml
environment_id
product_id
name
isactive
);
our $columns = join(", ", DB_COLUMNS);
our constant $max_depth = 7;
###############################
#### Methods ####
###############################
@@ -101,16 +104,18 @@ sub _init {
my $self = shift;
my ($param) = (@_);
my $dbh = Bugzilla->dbh;
my $id = $param unless (ref $param eq 'HASH');
my $id = $param unless (ref $param eq 'HASH' || ref $param eq 'Bugzilla::Testopia::Environment::Xml');
my $obj;
if (defined $id && detaint_natural($id)) {
$obj = $dbh->selectrow_hashref(qq{
SELECT $columns FROM test_environments
WHERE environment_id = ?}, undef, $id);
} elsif (ref $param eq 'HASH'){
SELECT $columns
FROM test_environments
WHERE environment_id = ?}, undef, ($id));
} elsif (ref $param eq 'HASH' || ref $param eq 'Bugzilla::Testopia::Environment::Xml'){
$obj = $param;
}
@@ -119,41 +124,263 @@ sub _init {
foreach my $field (keys %$obj) {
$self->{$field} = $obj->{$field};
}
if(! ref $obj eq 'Bugzilla::Testopia::Environment::Xml'){
$self->get_environment_elements();
my @elements = $self->{'elements'};
foreach my $elem (@elements)
{
foreach my $element (@$elem)
{
my $elem_id = $element->{'element_id'};
my @properties = $element->{'properties'};
foreach my $prop (@properties)
{
foreach my $property (@$prop)
{
my $prop_id = $property->{'property_id'};
$property->{'value_selected'} = $self->get_value_selected($self->{'environment_id'},$elem_id,$prop_id);
}
}
}
}
}
return $self;
}
=head2 get_op_sys_list
Returns the list of bugzilla op_sys entries
=head2 get element list for environment
Returns an array of element objects for an environment
=cut
sub get_op_sys_list{
my $self = shift;
sub get_environment_elements{
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectall_arrayref(
"SELECT id, value AS name
FROM op_sys
ORDER BY sortkey", {'Slice'=>{}});
my $self = shift;
return $self->{'elements'} if exists $self->{'elements'};
my $id = $self->{'environment_id'};
my $ref = $dbh->selectcol_arrayref(qq{
SELECT DISTINCT tee.element_id
FROM test_environment_map as tem
JOIN test_environment_element as tee
ON tem.element_id = tee.element_id
WHERE tem.environment_id = ?},undef,$id);
my @elements;
return $ref;
foreach my $val (@$ref){
push @elements, Bugzilla::Testopia::Environment::Element->new($val);
}
$self->{'elements'} = \@elements;
return \@elements;
}
=head2 get_rep_platform_list
sub element_count {
my $self = shift;
return scalar(@{$self->get_environment_elements});
}
Returns the list of rep_platforms from bugzilla
sub elements_to_json {
my $self = shift;
my $elements = $self->get_environment_elements;
my $json = '[';
foreach my $element (@$elements)
{
$json .= '{title:"'. $element->{'name'} .'",';
$json .= 'objectId:"'. $element->{'element_id'}. '",';
$json .= 'widgetId:"element'. $element->{'element_id'} .'",';
$json .= 'actionsDisabled:["addCategory","addValue","addChild"],';
$json .= 'isFolder:true,' if($element->check_for_children || $element->check_for_properties);
$json .= 'childIconSrc:"testopia/img/circle.gif"},';
}
chop $json;
$json .= ']';
return $json;
}
=head2 get_value_selected
Returns a selected value for the specified environment element property instance.
=cut
sub get_rep_platform_list{
sub get_value_selected{
my $dbh = Bugzilla->dbh;
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectall_arrayref(
"SELECT id, value AS name
FROM rep_platform
ORDER BY sortkey", {'Slice'=>{}});
my ($environment,$element,$property) = (@_);
my ($var) = $dbh->selectrow_array(
"SELECT value_selected
FROM test_environment_map
WHERE environment_id = ?
AND element_id = ?
AND property_id = ?",
undef,($environment,$element,$property));
return $var;
}
return $ref;
=head2 get environment names
Returns the list of environment names and ids
=cut
sub get_environments{
my $dbh = Bugzilla->dbh;
my $self = shift;
my $ref = $dbh->selectall_arrayref(
"SELECT environment_id, name
FROM test_environments");
return $ref;
}
sub get_all_env_categories {
my $self = shift;
my ($byid) = @_;
my $dbh = Bugzilla->dbh;
my $idstr = $byid ? 'env_category_id' : 'DISTINCT name';
my $ref = $dbh->selectall_arrayref(
"SELECT $idstr AS id, name
FROM test_environment_category",
{'Slice' => {}});
return $ref;
}
sub get_all_visible_elements {
my $self = shift;
my ($byid) = @_;
my $dbh = Bugzilla->dbh;
my $idstr = $byid ? 'element_id' : 'DISTINCT name';
my $ref = $dbh->selectall_arrayref(
"SELECT $idstr AS id, name
FROM test_environment_element",
{'Slice' => {}});
return $ref;
}
sub get_all_element_properties {
my $self = shift;
my ($byid) = @_;
my $dbh = Bugzilla->dbh;
my $idstr = $byid ? 'property_id' : 'DISTINCT name';
my $ref = $dbh->selectall_arrayref(
"SELECT $idstr AS id, name, validexp
FROM test_environment_property",
{'Slice' => {}});
return $ref;
}
sub get_distinct_property_values {
my $self = shift;
my @exps;
foreach my $prop (@{$self->get_all_element_properties}){
push @exps, split(/\|/, $prop->{'validexp'})
}
my %seen;
foreach my $v (@exps){
$seen{$v} = $v;
}
my @values;
foreach my $v (keys %seen){
my %p;
$p{'id'} = $v;
$p{'name'} = $v;
push @values, \%p;
}
return \@values;
}
=head2 get all elements
Returns the list of element names, ids and category names
=cut
sub get_all_elements{
my $dbh = Bugzilla->dbh;
my $self = shift;
my $ref = $dbh->selectcol_arrayref(
"SELECT tee.element_id
FROM test_environment_map as tem
JOIN test_environment_element as tee
ON tem.element_id = tee.element_id",
undef);
my @elements;
foreach my $val (@$ref){
push @elements, Bugzilla::Testopia::Environment::Element->new($val);
}
return \@elements;
}
=head2 check environment name
Returns environment id if environment exists
=cut
sub check_environment{
my $self = shift;
my ($name, $product_id) = (@_);
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(
"SELECT environment_id
FROM test_environments
WHERE name = ? AND product_id = ?",
undef, ($name, $product_id));
return $used;
}
=head2 Check Environment Element Property Value Selected
Returns environment id if Environment Element Property Value Selected exists
=cut
sub check_value_selected {
my $self = shift;
my ($prop_id, $elem_id) = @_;
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(
"SELECT environment_id
FROM test_environment_map
WHERE environment_id = ?
AND property_id = ?
AND element_id = ?",
undef, ($self->{'environment_id'}, $prop_id, $elem_id));
return $used;
}
=head2 store
@@ -165,17 +392,64 @@ Serializes this environment to the database
sub store {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
# Verify name is available
return 0 if $self->check_name($self->{'name'});
#Verify Environment isn't already in use.
return undef if $self->check_environment($self->{'name'}, $self->{'product_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environments ($columns)
VALUES (?,?,?,?,?)",
undef, (undef, $self->{'op_sys_id'},
$self->{'rep_platform_id'}, $self->{'name'},
$self->{'xml'}));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
return $key;
VALUES (?,?,?,?)",
undef, (undef, $self->{'product_id'}, $self->{'name'}, $self->{'isactive'}));
my $key = $dbh->bz_last_key( 'test_environments', 'environment_id' );
my $elements = $self->{'elements'};
foreach my $element (@$elements)
{
$self->persist_environment_element_and_children(1, $element, "store");
}
return $key;
}
=head2 store property values
Serializes the property values to the database
=cut
sub store_property_value {
my $self = shift;
my ($prop_id,$elem_id,$value_selected) = @_;
return 0 if ($self->check_value_selected($prop_id, $elem_id, $value_selected));
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environment_map (environment_id,property_id,element_id,value_selected)
VALUES (?,?,?,?)",undef, ($self->{'environment_id'}, $prop_id, $elem_id,$value_selected));
return 1;
}
=head2 store environment name
Serializes the environment name to the database
=cut
sub store_environment_name {
my $self = shift;
my ($name, $product_id) = (@_);
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
return undef if $self->check_environment($name, $product_id);
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environments ($columns)
VALUES (?,?,?,?)", undef,
(undef,$product_id,$name,1));
return $dbh->bz_last_key( 'test_environments', 'environment_id' );
}
=head2 update
@@ -190,12 +464,18 @@ sub update {
my $self = shift;
my ($newvalues) = @_;
my $dbh = Bugzilla->dbh;
my $product_id;
$dbh->bz_lock_tables('test_environments WRITE');
foreach my $field (keys %{$newvalues}){
if ($self->{$field} ne $newvalues->{$field}){
# If the new name is already in use, return.
if ($field eq 'name' && $self->check_name($newvalues->{'name'})) {
$product_id = $newvalues->{'product_id'} || $self->{'product_id'};
if ($product_id eq undef) {
$dbh->bz_unlock_tables;
return 0;
}
if ($field eq 'name' && $self->check_environment($newvalues->{'name'}, $product_id)) {
$dbh->bz_unlock_tables;
return 0;
}
@@ -203,15 +483,86 @@ sub update {
$dbh->do("UPDATE test_environments
SET $field = ? WHERE environment_id = ?",
undef, $newvalues->{$field}, $self->{'environment_id'});
$self->{$field} = $newvalues->{$field};
$self->{$field} = $newvalues->{$field};
}
}
$dbh->bz_unlock_tables;
my $elements = $self->{'elements'};
foreach my $element (@$elements)
{
$self->persist_environment_element_and_children(1, $element, "update");
}
return 1;
}
=head2 persist_environment_element_and_children
Persists Environment Element and Children Recursively.
=cut
sub persist_environment_element_and_children {
my $self = shift;
my ($depth, $element, $method) = @_;
if ($depth > $max_depth) {
return;
}
$depth++;
my $elem_id = $element->{'element_id'};
my $properties = $element->{'properties'};
foreach my $property (@$properties)
{
my $prop_id = $property->{'property_id'};
my $value_selected = $property->{'value_selected'};
my ($value_stored) = $self->get_value_selected($self->{'environment_id'},$prop_id,$elem_id);
if ($method eq "store" || $value_stored eq undef) {
$self->store_property_value($prop_id,$elem_id,$value_selected);
}
else {
$self->update_property_value($prop_id,$elem_id,$value_selected);
}
}
my $children = $element->{'children'};
foreach my $child_element (@$children) {
$self->persist_environment_element_and_children($depth, $child_element, $method);
}
}
=head2 update property value
Updates the property of the element in the database
=cut
sub update_property_value {
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my $self = shift;
my ($propID, $elemID, $valueSelected) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_map
SET value_selected = ? WHERE environment_id = ? AND property_id = ? AND element_id = ?"
,undef, ($valueSelected,$self->{'environment_id'},$propID,$elemID));
return 1;
}
sub delete_element {
my $self = shift;
my ($element_id) = @_;
my $dbh = Bugzilla->dbh;
$dbh->do("DELETE FROM test_environment_map
WHERE environment_id = ? AND element_id = ?",
undef,($self->id, $element_id));
}
=head2 obliterate
Completely removes this environment from the database.
@@ -222,19 +573,23 @@ sub obliterate {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_runs READ', 'test_environments WRITE');
$dbh->bz_lock_tables('test_runs READ', 'test_environments WRITE','test_environment_map WRITE');
my $used = $dbh->selectrow_array("SELECT 1 FROM test_runs
WHERE environment_id = ?",
WHERE environment_id = ?",
undef, $self->{'environment_id'});
if ($used) {
$dbh->bz_unlock_tables;
ThrowUserError("testopia-non-zero-run-count", {'object' => 'Environment'});
}
$dbh->do("DELETE FROM test_environments
$dbh->do("UPDATE test_environments SET isactive = 0
WHERE environment_id = ?", undef, $self->{'environment_id'});
$dbh->bz_unlock_tables;
return 1;
}
=head2 get_run_list
Returns a list of run ids associated with this environment.
@@ -245,7 +600,7 @@ sub get_run_list {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref("SELECT run_id FROM test_runs
WHERE environment_id = ?",
WHERE environment_id = ?",
undef, $self->{'environment_id'});
return join(",", @{$ref});
}
@@ -260,29 +615,11 @@ sub get_run_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($count) = $dbh->selectrow_array("SELECT COUNT(run_id) FROM test_runs
WHERE environment_id = ?",
WHERE environment_id = ?",
undef, $self->{'environment_id'});
return $count;
}
=head2 check_name
Returns true if an environment of the specified name exists
in the database.
=cut
sub check_name {
my $self = shift;
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array("SELECT 1
FROM test_environments
WHERE name = ?",
undef, $name);
return $used;
}
=head2 canedit
Returns true if the logged in user has rights to edit this environment.
@@ -333,57 +670,32 @@ Returns the ID of this object
Returns the name of this object
=head2 xml
Returnst the xml associated with this environment
=cut
sub id { return $_[0]->{'environment_id'}; }
sub product_id { return $_[0]->{'product_id'}; }
sub isactive { return $_[0]->{'isactive'}; }
sub name { return $_[0]->{'name'}; }
sub xml { return $_[0]->{'xml'}; }
=head2 op_sys
=head2 product
Returns the bugzilla op_sys value
Returns the bugzilla product
=cut
sub op_sys {
sub product {
my $self = shift;
my $dbh = Bugzilla->dbh;
return $self->{'op_sys'} if exists $self->{'op_sys'};
my ($res) = $dbh->selectrow_array("SELECT value
FROM op_sys
WHERE id = ?",
undef, $self->{'op_sys_id'});
$self->{'op_sys'} = $res;
return $self->{'op_sys'};
}
=head2 rep_platform
Returns the bugzilla rep_platform value
=cut
sub rep_platform {
my $self = shift;
my $dbh = Bugzilla->dbh;
return $self->{'rep_platform'} if exists $self->{'rep_platform'};
my ($res) = $dbh->selectrow_array("SELECT value
FROM rep_platform
WHERE id = ?",
undef, $self->{'rep_platform_id'});
$self->{'rep_platform'} = $res;
return $self->{'rep_platform'};
return $self->{'product'} if exists $self->{'product'};
$self->{'product'} = Bugzilla::Product->new($self->{'product_id'});
return $self->{'product'};
}
=head1 TODO
Use Bugzilla methods for op_sys and rep_platforms in 2.22
=head1 SEE ALSO

View File

@@ -0,0 +1,543 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
# Michael Hight <mjhight@gmail.com>
# Garrett Braden <gbraden@novell.com>
=head1 NAME
Bugzilla::Testopia::Environment::Category - A test element category
=head1 DESCRIPTION
Categories are used to organize environment elements.
=head1 SYNOPSIS
$prop = Bugzilla::Testopia::Environment::Category->new($env_category_id);
$prop = Bugzilla::Testopia::Environment::Category->new(\%cat_hash);
=cut
package Bugzilla::Testopia::Environment::Category;
use strict;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Config;
use Bugzilla::User;
use Bugzilla::Constants;
###############################
#### Initialization ####
###############################
=head1 FIELDS
env_category_id
product_id
name
=cut
use constant DB_COLUMNS => qw(
env_category_id
product_id
name
);
our $columns = join(", ", DB_COLUMNS);
###############################
#### Methods ####
###############################
=head1 METHODS
=head2 new
Instantiates a new Category object
=cut
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
return $self->_init(@_);
}
=head2 _init
Private constructor for the category class
=cut
sub _init {
my $self = shift;
my ($param) = (@_);
my $dbh = Bugzilla->dbh;
my $id = $param unless (ref $param eq 'HASH');
my $obj;
if (defined $id && detaint_natural($id)) {
$obj = $dbh->selectrow_hashref(qq{
SELECT $columns
FROM test_environment_category
WHERE env_category_id = ?}, undef, $id);
} elsif (ref $param eq 'HASH'){
$obj = $param;
}
return undef unless (defined $obj);
foreach my $field (keys %$obj) {
$self->{$field} = $obj->{$field};
}
return $self;
}
=head2 get element list by category
Returns an array of element objects for a category
=cut
sub get_elements_by_category{
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref(qq{
SELECT element_id
FROM test_environment_element
WHERE env_category_id = ?}, undef, $self->{'env_category_id'});
my @elements;
foreach my $val (@$ref){
push @elements, Bugzilla::Testopia::Environment::Element->new($val);
};
return \@elements;
}
=head2 get_parent_elements
Returns an array of parent elements by category
=cut
sub get_parent_elements{
my $self = shift;
my $dbh = Bugzilla->dbh;
################## original
# Matches null for parent id
#
# my $ref = $dbh->selectcol_arrayref(qq{
# SELECT element_id
# FROM test_environment_element
# WHERE env_category_id = ? AND parent_id is null }, undef, $self->{'env_category_id'});
#
############### end original delete when ready
################## edited original
# Matches 0 for parent id
#
# my $ref = $dbh->selectcol_arrayref(qq{
# SELECT element_id
# FROM test_environment_element
# WHERE env_category_id = ? AND parent_id = 0 }, undef, $self->{'env_category_id'});
#
############### end edited original keep this one when ready
########### temp fix to address issues with 0 or null in test_envrionment_element parent_id column
##### matching null OR 0
my $ref = $dbh->selectcol_arrayref(qq{
SELECT element_id
FROM test_environment_element
WHERE env_category_id = ? AND (parent_id is null or parent_id = 0) }, undef, $self->{'env_category_id'});
########### end temp fix.... delete when ready
my @elements;
foreach my $val (@$ref){
push @elements, Bugzilla::Testopia::Environment::Element->new($val);
};
return \@elements;
}
=head2 check_for_elements
Returns 1 if a category has any elements
=cut
sub check_for_elements{
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectrow_array(qq{
SELECT 1
FROM test_environment_element
WHERE env_category_id = ?}, undef, $self->{'env_category_id'});
return $ref;
}
=head2 get_product_list
Returns the product_id, product name, and count of categories
=cut
sub get_env_product_list{
my $self = shift;
my ($class_id) = @_;
my $dbh = Bugzilla->dbh;
my $query = "SELECT p.id, p.name, COUNT(tec.env_category_id) AS cat_count
FROM products p
LEFT JOIN group_control_map
ON group_control_map.product_id = p.id ";
if (Param('useentrygroupdefault')) {
$query .= "AND group_control_map.entry != 0 ";
} else {
$query .= "AND group_control_map.membercontrol = " .
CONTROLMAPMANDATORY . " ";
}
if (%{Bugzilla->user->groups}) {
$query .= "AND group_id NOT IN(" .
join(',', values(%{Bugzilla->user->groups})) . ") ";
}
$query .= "LEFT OUTER JOIN test_environment_category AS tec
ON p.id = tec.product_id ";
$query .= "WHERE group_id IS NULL ";
$query .= "AND classification_id = ? " if $class_id;
$query .= "GROUP BY p.id
ORDER BY p.name";
my $ref;
if($class_id){
$ref = $dbh->selectall_arrayref($query, {'Slice' => {}}, $class_id);
}
else{
$ref = $dbh->selectall_arrayref($query, {'Slice' => {}});
}
unshift @$ref, {'id' => 0, 'name' => '--ALL--', 'cat_count' => $self->get_all_child_count };
return $ref;
}
sub get_all_child_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($all_count) = $dbh->selectrow_array(
"SELECT COUNT(*)
FROM test_environment_category
WHERE product_id = 0");
return $all_count;
}
sub product_categories_to_json {
my $self = shift;
my ($product_id, $disable_move) = @_;
detaint_natural($product_id);
$disable_move = ',"addChild","move"' if $disable_move;
my $json = "[";
foreach my $cat (@{$self->get_element_categories_by_product($product_id)}){
$json .= '{title:"' . $cat->name . '",';
$json .= 'isFolder:' . ($cat->check_for_elements() ? "true" : "false") . ',';
$json .= 'objectId:"' . $cat->id . '",';
$json .= 'widgetId:"category' . $cat->id . '",';
$json .= 'actionsDisabled:["addCategory","addProperty","addValue"';
$json .= $disable_move if $disable_move;
$json .= ',"remove"' unless $cat->candelete;
$json .= '],';
$json .= 'childIconSrc:"testopia/img/square.gif"},';
}
chop $json;
$json .= "]";
return $json;
}
=head2 get_element_categories_by_product
Returns the list of element category names and ids by product id
=cut
sub get_element_categories_by_product{
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($product_id) = (@_);
my $ref = $dbh->selectcol_arrayref(
"SELECT env_category_id
FROM test_environment_category
WHERE product_id = ?",
undef, $product_id);
my @objs;
foreach my $id (@{$ref}){
push @objs, Bugzilla::Testopia::Environment::Category->new($id);
}
return \@objs;
}
=head2 new_category_count
Returns 1 if element has children
=cut
sub new_category_count{
my $self = shift;
my ($prod_id) = @_;
$prod_id ||= $self->{'product_id'};
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(
"SELECT COUNT(*)
FROM test_environment_category
WHERE name like 'New category%'
AND product_id = ?",
undef, $prod_id);
return $used + 1;
}
sub elements_to_json {
my $self = shift;
my ($disable_add) = @_;
$disable_add = ',"addChild"' if $disable_add;
my $elements = $self->get_parent_elements;
my $json = '[';
foreach my $element (@$elements)
{
$json .= '{title:"'. $element->{'name'} .'",';
$json .= 'objectId:"'. $element->{'element_id'}. '",';
$json .= 'widgetId:"element'. $element->{'element_id'} .'",';
$json .= 'actionsDisabled:["addCategory","addValue"';
$json .= $disable_add if $disable_add;
$json .= ',"remove"' unless $element->candelete;
$json .= '],';
$json .= 'isFolder:true,' if($element->check_for_children || $element->check_for_properties);
$json .= 'childIconSrc:"testopia/img/circle.gif"},';
}
chop $json;
$json .= ']';
return $json;
}
=head2 check_category
Returns category id if a category exists
=cut
sub check_category{
my $dbh = Bugzilla->dbh;
my $self = shift;
my ($name, $prodID) = (@_);
$prodID ||= $self->product_id;
my ($used) = $dbh->selectrow_array(
"SELECT env_category_id
FROM test_environment_category
WHERE name = ? AND product_id = ?",
undef,($name,$prodID));
return $used;
}
=head2 check_category_by_id
Returns category name if a category id exists
=cut
sub check_category_by_id{
my $dbh = Bugzilla->dbh;
my $self = shift;
my ($id) = (@_);
my ($used) = $dbh->selectrow_arrayref(qq{
SELECT name
FROM test_environment_category
WHERE env_category_id = ?},undef,$id);
return $used;
}
=head2 store
Serializes this category to the database and returns the key or 0
=cut
sub store {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
return 0 if $self->check_category($self->{'name'},$self->{'product_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environment_category ($columns)
VALUES (?,?,?)",undef, (undef, $self->{'product_id'},$self->{'name'}));
my $key = $dbh->bz_last_key( 'test_environment_category', 'env_category_id' );
return $key;
}
=head2 set_name
Updates the category name in the database
=cut
sub set_name {
my $self = shift;
my ($name) = (@_);
my $dbh = Bugzilla->dbh;
return undef if $self->check_category($name);
$dbh->do("UPDATE test_environment_category SET name = ?
WHERE env_category_id = ? AND product_id = ?",
undef, ($name, $self->{'env_category_id'},$self->{'product_id'}));
return 1;
}
=head2 set_product
Updates the category in the database
=cut
sub set_product {
my $self = shift;
my ($product_id) = (@_);
return if ($product_id == $self->{'product_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_category SET product_id = ?
WHERE env_category_id = ? AND product_id = ?",
undef, ($product_id, $self->{'env_category_id'},$self->{'product_id'}));
return 1;
}
=head2 obliterate
Completely removes the element category entry from the database.
=cut
sub obliterate {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_environment_element AS tee READ', 'test_environment_map AS tem READ', 'test_environment_category WRITE');
if (!$self->candelete) {
$dbh->bz_unlock_tables;
return 0;
}
$dbh->do("DELETE FROM test_environment_category
WHERE env_category_id = ?", undef, $self->id);
$dbh->bz_unlock_tables;
return 1;
}
sub candelete {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $used = $dbh->selectrow_array(
"SELECT 1 FROM test_environment_map AS tem
JOIN test_environment_element AS tee ON tee.element_id = tem.element_id
WHERE tee.env_category_id = ?",
undef, $self->id);
return !$used;
}
###############################
#### Accessors ####
###############################
=head2 id
Returns the ID of this category
=head2 name
Returns the name of this category
=head2 product_id
Returns the product_id of this category
=cut
sub id { return $_[0]->{'env_category_id'}; }
sub name { return $_[0]->{'name'}; }
sub product_id { return $_[0]->{'product_id'}; }
=head2 product_name
Returns the name of the product this plan is associated with
=cut
sub product_name {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
return $self->{'product_name'} if exists $self->{'product_name'};
my ($name) = $dbh->selectrow_array(
"SELECT name FROM products
WHERE id = ?",
undef, $self->id);
$self->{'product_name'} = $name;
return $self->{'product_name'};
}
1;

View File

@@ -0,0 +1,471 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
# Michael Hight <mjhight@gmail.com>
# Garrett Braden <gbraden@novell.com>
=head1 NAME
Bugzilla::Testopia::Environment::Element - A test environment element
=head1 DESCRIPTION
Environment elements are a set of environment entities that dictate
the conditions a test was conducted in. Each environment must have an
element. Elements can be very simple or very complex by adding properties.
Elements can have child elements.
=head1 SYNOPSIS
$elem = Bugzilla::Testopia::Environment::Element->new($elem_id);
$elem = Bugzilla::Testopia::Environment::Element->new(\%elem_hash);
=cut
package Bugzilla::Testopia::Environment::Element;
use strict;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
use JSON;
###############################
#### Initialization ####
###############################
=head1 FIELDS
element_id
env_category_id
name
parent_id
isprivate
=cut
use constant DB_COLUMNS => qw(
element_id
env_category_id
name
parent_id
isprivate
);
our $columns = join(", ", DB_COLUMNS);
our constant $max_depth = 5;
###############################
#### Methods ####
###############################
=head1 METHODS
=head2 new
Instantiates a new Element object
=cut
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
return $self->_init(@_);
}
=head2 _init
Private constructor for the element class
=cut
sub _init {
my $self = shift;
my ($param) = (@_);
my $dbh = Bugzilla->dbh;
my $id = $param unless (ref $param eq 'HASH');
my $obj;
if (defined $id && detaint_natural($id)) {
$obj = $dbh->selectrow_hashref(
"SELECT $columns
FROM test_environment_element
WHERE element_id = ?", undef, $id);
} elsif (ref $param eq 'HASH'){
$obj = $param;
}
return undef unless (defined $obj);
foreach my $field (keys %$obj) {
$self->{$field} = $obj->{$field};
}
$self->get_properties();
return $self;
}
=head2 get_children
Returns an array of the children objects for an element and recursively
gets all of their children until exhausted.
=cut
sub get_children{
my $dbh = Bugzilla->dbh;
my $self = shift;
return $self->{'children'} if exists $self->{'children'};
my %newvalues = (@_);
my $depth = $max_depth;
if(%newvalues)
{
$depth = $newvalues{'depth'};
}
$depth--;
if($depth == 0)
{return;}
my $id = $self->{'element_id'};
my $ref = $dbh->selectcol_arrayref(qq{
SELECT tee.element_id
FROM test_environment_element as tee
WHERE tee.parent_id = ?},undef,$id);
my @children;
foreach my $val (@$ref){
my $child = Bugzilla::Testopia::Environment::Element->new($val);
$child->get_children('depth'=>$depth);
push(@children,$child);
}
$self->{'children'} = \@children;
}
=head2 get_properties
Returns an array of the property objects for an element.
=cut
sub get_properties{
my $dbh = Bugzilla->dbh;
my $self = shift;
my $ref = $dbh->selectcol_arrayref(qq{
SELECT tep.property_id
FROM test_environment_property as tep
WHERE tep.element_id = ?},undef,($self->{'element_id'}));
my @properties;
foreach my $val (@$ref){
my $property = Bugzilla::Testopia::Environment::Property->new($val);
push(@properties,$property);
}
$self->{'properties'} = \@properties;
return $self->{'properties'};
}
=head2 check_element
Returns element id if element exists
=cut
sub check_element{
my $dbh = Bugzilla->dbh;
my $self = shift;
my ($name, $cat_id) = @_;
# Since categories are uniquely identified by product_id we don't have to check by join on the product_id.
my ($used) = $dbh->selectrow_array(
"SELECT element_id
FROM test_environment_element
WHERE name = ?
AND env_category_id = ?",
undef,($name,$cat_id));
return $used;
}
=head2 check_for_children
Returns 1 if element has children
=cut
sub check_for_children{
my $dbh = Bugzilla->dbh;
my $self = shift;
my ($used) = $dbh->selectrow_array(qq{
SELECT 1
FROM test_environment_element
WHERE parent_id = ? },undef,$self->{'element_id'});
return $used;
}
sub children_to_json{
my $self = shift;
my ($disable_move) = @_;
$disable_move = ',"addChild","move","remove"' if $disable_move;
my $disable_add = ',"addChild"' if $disable_move;
my $elements = $self->get_children;
my $properties = $self->get_properties;
my $json = '[';
foreach my $elem (@$elements)
{
$json .= '{title:"' . $elem->{'name'} . '",';
$json .= 'objectId:"' . $elem->{'element_id'} . '",';
$json .= 'widgetId:"element' . $elem->{'element_id'} . '",';
$json .= 'actionsDisabled:["addCategory","addValue"';
$json .= $disable_add if $disable_add;
$json .=',"remove"' unless $elem->candelete;
$json .= '],';
$json .= 'isFolder:true,' if $elem->check_for_children();
$json .= 'childIconSrc:"testopia/img/circle.gif"},';
}
foreach my $prop (@$properties)
{
$json .= '{title: "' . $prop->{'name'} .'",';
$json .= 'objectId:"' . $prop->{'property_id'} . '",';
$json .= 'widgetId:"property' . $prop->{'property_id'} . '",';
$json .= 'actionsDisabled:["addCategory","addElement","addProperty"';
$json .= $disable_move if $disable_move;
$json .= ',"remove"' unless $prop->candelete;
$json .= '],';
$json .= 'isFolder:true,' if($prop->check_for_validexp($prop));
$json .= 'childIconSrc:"testopia/img/triangle.gif"},';
}
chop $json if ($json ne '[');
$json .= ']';
return $json;
}
=head2 new_element_count
Returns 1 if element has children
=cut
sub new_element_count{
my $self = shift;
my ($cat_id) = @_;
$cat_id ||= $self->{'env_category_id'};
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(
"SELECT COUNT(*)
FROM test_environment_element
WHERE name like 'New element%'
AND env_category_id = ?",
undef, $cat_id);
return $used + 1;
}
=head2 check_for_properties
Returns 1 if element has properties
=cut
sub check_for_properties{
my $dbh = Bugzilla->dbh;
my $self = shift;
my ($used) = $dbh->selectrow_array(qq{
SELECT 1
FROM test_environment_property
WHERE element_id = ? },undef,$self->{'element_id'});
return $used;
}
=head2 store
Serializes the new element to the database and returns the primary key or 0
=cut
sub store {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
# Verify name is available
return undef if $self->check_element($self->{'name'},$self->{'env_category_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environment_element ($columns)
VALUES (?,?,?,?,?)",
undef, (undef, $self->{'env_category_id'}, $self->{'name'},
$self->{'parent_id'},$self->{'isprivate'}));
my $key = $dbh->bz_last_key('test_environment_element', 'element_id');
return $key;
}
=head2 update_element_name
Updates the element in the database
=cut
sub update_element_name {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my ($name) = (@_);
return 0 if check_element($name, $self->{'env_category_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_element
SET name = ? WHERE element_id = ?",undef, ($name,$self->{'element_id'} ));
return 1;
}
=head2 update_element_category
Updates the category of the element in the database
=cut
sub update_element_category {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my ($catid) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_element
SET env_category_id = ? WHERE element_id = ?",undef, ($catid,$self->{'element_id'} ));
return 1;
}
=head2 update_element_parent
Updates the parent_id of the element in the database
=cut
sub update_element_parent {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my ($parent_id) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_element
SET parent_id = ? WHERE element_id = ?",undef, ($parent_id,$self->{'element_id'} ));
return 1;
}
=head2 obliterate
Completely removes the element entry from the database.
=cut
sub obliterate {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_environment_map READ', 'test_environment_element WRITE');
if (!$self->candelete) {
$dbh->bz_unlock_tables;
return 0;
}
$dbh->do("DELETE FROM test_environment_element
WHERE element_id = ?", undef, $self->{'element_id'});
$dbh->bz_unlock_tables;
return 1;
}
sub candelete {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $used = $dbh->selectrow_array("SELECT 1 FROM test_environment_map
WHERE element_id = ?",
undef, $self->id);
return !$used;
}
###############################
#### Accessors ####
###############################
=head2 id
Returns the ID of this object
=head2 name
Returns the name of this object
=head2 product_id
Returns the product_id of this object
=head2 env_category_id
Returns the category_id associated with this element
=head2 parent_id
Returns the element's parent_id associated with this element
=cut
sub id { return $_[0]->{'element_id'}; }
sub name { return $_[0]->{'name'}; }
sub product_id { return $_[0]->{'product_id'}; }
sub env_category_id { return $_[0]->{'env_category_id'}; }
sub parent_id { return $_[0]->{'parent_id'}; }
sub isprivate { return $_[0]->{'isprivate'}; }
sub get_parent { return $_[0]->new($_[0]->{'parent_id'}); }
1;

View File

@@ -0,0 +1,372 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
# Michael Hight <mjhight@gmail.com>
# Garrett Braden <gbraden@novell.com>
=head1 NAME
Bugzilla::Testopia::Environment::Property - A test environment element property
=head1 DESCRIPTION
Element properties describe the elements in more detail. An
element can have an unlimited number of properties to describe it.
The valid expression limits the possible descriptions to valid choices
for each property.
=head1 SYNOPSIS
$prop = Bugzilla::Testopia::Environment::Property->new($prop_id);
$prop = Bugzilla::Testopia::Environment::Property->new(\%prop_hash);
=cut
package Bugzilla::Testopia::Environment::Property;
use strict;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
###############################
#### Initialization ####
###############################
=head1 FIELDS
property_id
element_id
name
validexp
=cut
use constant DB_COLUMNS => qw(
property_id
element_id
name
validexp
);
our $columns = join(", ", DB_COLUMNS);
###############################
#### Methods ####
###############################
=head1 METHODS
=head2 new
Instantiates a new Property object
=cut
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
return $self->_init(@_);
}
=head2 _init
Private constructor for the property class
=cut
sub _init {
my $self = shift;
my ($param) = (@_);
my $dbh = Bugzilla->dbh;
my $id = $param unless (ref $param eq 'HASH');
my $obj;
if (defined $id && detaint_natural($id)) {
$obj = $dbh->selectrow_hashref(qq{
SELECT $columns
FROM test_environment_property
WHERE property_id = ?}, undef, $id);
} elsif (ref $param eq 'HASH'){
$obj = $param;
}
return undef unless (defined $obj);
foreach my $field (keys %$obj) {
$self->{$field} = $obj->{$field};
}
return $self;
}
=head2 check_property
Returns id if a property exists
=cut
sub check_property{
my $self = shift;
my ($name, $element_id) = @_;
my $dbh = Bugzilla->dbh;
if ($name eq undef || $name eq '' || $element_id eq undef) {
return "check_product must be passed a valid name and product_id";
}
my ($used) = $dbh->selectrow_array(qq{
SELECT property_id
FROM test_environment_property
WHERE name = ? AND element_id = ?},undef,$name,$element_id);
return $used;
}
=head2 check_for_validexp
Returns 1 if a validexp exist for the property
=cut
sub check_for_validexp{
my $self = shift;
my $name = (@_);
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(qq{
SELECT 1
FROM test_environment_property
WHERE property_id = ? AND (validexp IS NOT NULL AND validexp <> '')},undef,$self->{'property_id'});
return $used;
}
=head2 new_property_count
Returns the count + 1 of new properties
=cut
sub new_property_count{
my $self = shift;
my ($element_id) = @_;
$element_id ||= $self->{'element_id'};
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(
"SELECT COUNT(*)
FROM test_environment_property
WHERE name like 'New property%'
AND element_id = ?",
undef, $element_id);
return $used + 1;
}
=head2 get_validexp
Returns the validexp for the property
=cut
sub get_validexp{
my $self = shift;
my $name = (@_);
my $dbh = Bugzilla->dbh;
my $validexp = $dbh->selectrow_arrayref(qq{
SELECT validexp
FROM test_environment_property
WHERE property_id = ? },undef,$self->{'property_id'});
return $validexp;
}
=head2 store
Serializes the new property to the database
=cut
sub store {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
# Verify name is available
return undef if $self->check_property($self->{'name'}, $self->{'element_id'});
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_environment_property ($columns)
VALUES (?,?,?,?)",undef, (undef, $self->{'element_id'}, $self->{'name'}, $self->{'validexp'}));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
return $key;
}
=head2 set_name
Updates the property name in the database
=cut
sub set_name {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my ($name) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_property
SET name = ? WHERE property_id = ?",undef, ($name,$self->{'property_id'} ));
return 1;
}
=head2 set_element
Updates the elmnt_id in the database
=cut
sub set_element {
my $self = shift;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my ($id) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_property
SET element_id = ? WHERE property_id = ?",undef, ($id,$self->{'property_id'} ));
return 1;
}
=head2 update_property_validexp
Updates the property valid expression in the database
=cut
sub update_property_validexp {
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
my $self = shift;
my ($validexp) = (@_);
my $dbh = Bugzilla->dbh;
$dbh->do("UPDATE test_environment_property
SET validexp = ? WHERE property_id = ?",undef, ($validexp,$self->{'property_id'} ));
return 1;
}
sub valid_exp_to_json {
my $self = shift;
my ($disable_move, $env_id) = @_;
my $env = Bugzilla::Testopia::Environment->new($env_id) if $env_id;
$disable_move = ',"addChild","move","remove"' if $disable_move;
my $validexp = $self->get_validexp;
my @validexpressions = split(/\|/, @$validexp[0]);
my $json = '[';
foreach (@validexpressions)
{
$json .= '{title: "' . $_ . '",';
$json .= 'widgetId:"validexp' . $self->id . '~'. $_ .'",';
$json .= 'actionsDisabled:["addCategory","addElement","addProperty","addValue"'. $disable_move .'],';
$json .= 'objectId:"' . $self->id . '~' . $_ . '",';
if ($env && $env->get_value_selected($env->id, $self->element_id, $self->id) eq $_){
$json .= 'childIconSrc:"testopia/img/selected_value.png"},';
}
else{
$json .= 'childIconSrc:""},';
}
}
chop $json;
$json .= ']';
return $json;
}
=head2 obliterate
Completely removes the element property entry from the database.
=cut
sub obliterate {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_environment_map READ', 'test_environment_property WRITE');
if (!$self->candelete) {
$dbh->bz_unlock_tables;
return 0;
}
$dbh->do("DELETE FROM test_environment_property
WHERE property_id = ?", undef, $self->id);
$dbh->bz_unlock_tables;
return 1;
}
sub candelete {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $used = $dbh->selectrow_array("SELECT 1 FROM test_environment_map
WHERE property_id = ?",
undef, $self->id);
return !$used;
}
###############################
#### Accessors ####
###############################
=head2 id
Returns the ID of this object
=head2 name
Returns the name of this object
=head2 validexp
Returns the valid expression associated with this property
=head2 element_id
Returns the element's id associated with this property
=cut
sub id { return $_[0]->{'property_id'}; }
sub name { return $_[0]->{'name'}; }
sub validexp { return $_[0]->{'validexp'}; }
sub element_id { return $_[0]->{'element_id'}; }
1;

View File

@@ -0,0 +1,585 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Garrett Braden <gbraden@novell.com>
=head1 NAME
Bugzilla::Testopia::Environment::Xml - An XML representation of the Environment Object.
=head1 DESCRIPTION
This module is used to import and export environments via XML. It can parse an XML representation
of a Testopia Environment and persist it to the database. An Environment.pm XML object can be
initialized using new and passing it an XML scalar. It can also take two other parameters:
$admin - a boolean that automatically will store the imported environment to the database.
$max_depth - the max depth of child elements to import.
Example:
my $env_xml = Bugzilla::Testopia::Environment::Xml->new($xml, 1, 5);
Other subroutines can be called on the object. For example:
parse - takes the same three parameters as new
store - stores the imported xml Environment object to the database
Misc. Other:
the module also contains two other valueable fields on it's hash:
$self->{'message'} - running list of valueable information upon parsing and storing
$self->{'error'} - running list of error messages upon parsing and storing
One other method exists(check_new_items) to check if there are new Elements, Properties, Categories,
and Selected Values and returns a scalar value report of the new items not present in the database.
Import XML Environment Implementation Example: see tr_import_environment.cgi
To export an environment by env_id to XML use export
Example:
my $xml = Bugzilla::Testopia::Environment::Xml->export($env_id);
Export Environment XML Implementation Example: see tr_export_environment.cgi
=head1 SYNOPSIS
use Bugzilla::Testopia::Environment::Xml;
=cut
package Bugzilla::Testopia::Environment::Xml;
#************************************************** Uses ****************************************************#
use strict;
use warnings;
use CGI;
use lib ".";
use XML::Twig;
use Bugzilla::Util;
use Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Product;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Environment;
use Bugzilla::Testopia::Product;
use Bugzilla::Testopia::Environment::Category;
use Bugzilla::Testopia::Environment::Element;
use Bugzilla::Testopia::Environment::Property;
our constant $max_depth = 7;
=head2 new
Instantiates a new Bugzilla::Testopia::Environment::Xml object
=cut
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
return $self->_init(@_);
}
=head2 _init
Private constructor for the Bugzilla::Testopia::Environment::XML class
=cut
sub _init {
my ($self, $xml, $admin, $depth) = @_;
if (ref $xml eq 'HASH') {
$self = $xml;
}
elsif($xml) {
parse($self, $xml, $admin, $depth);
}
return undef unless (defined $xml);
return $self;
}
=head2 Parse Environment
=head2 DESCRIPTION
Parses the Environment XML
=cut
sub parse() {
my ($self, $xml, $admin, $depth) = @_;
if($depth eq undef) {
$self->{'max_depth'} = $max_depth;
}
else {
$self->{'max_depth'} = $depth
}
if ($admin) {
$self->{'message'} = "Importing XML Environment...<BR />";
}
else {
$self->{'message'} = "Parsing and Validating XML Environment...<BR />";
}
$self->{'error'} = undef;
if ($xml) {
trick_taint($xml);
}
my $twig = XML::Twig->new();
$twig->parse($xml);
my $root = $twig->root;
# Checking if Product and Environment already exist.
my $product_name = $root->{'att'}->{'product'};
my $product_id;
if (lc($product_name) eq "--all--") {
$self->{'message'} .= "..Using the <U>--ALL--</U> <STRONG>PRODUCT</STRONG>.<BR />";
$product_id = 0;
}
else {
$self->{'message'} .= "..Checking if <U>$product_name</U> <STRONG>PRODUCT</STRONG> already exists...";
($product_id) = Bugzilla::Testopia::Product->check_product_by_name($product_name);
if ($product_id) {
$self->{'message'} .= "EXISTS.<BR />";
}
else {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U>.<BR />Importing XML Environment Failed!<BR/>";
$self->{'error'} .= "<U>$product_name</U> <STRONG>PRODUCT</STRONG> doesn't exist. Please be sure to use an existing product.<BR />";
return 0;
}
}
($self->{'product_id'}) = $product_id;
$self->{'product_name'} = $product_name;
my $environment_name = $root->{'att'}->{'name'};
$self->{'name'} = $environment_name;
$self->{'message'} .= "..Checking if <U>$environment_name</U> <STRONG>ENVIRONMENT NAME</STRONG> already exists for the <U>$product_name</U> <STRONG>PRODUCT</STRONG>...";
my $environment = Bugzilla::Testopia::Environment->new({});
my ($env_id) = $environment->check_environment($environment_name, $product_id);
my $environment_id;
if ($env_id < 1) {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U><BR />";
# Storing New Environment if Admin
if ($admin) {
$self->{'message'} .= "....Storing new <U>$environment_name</U> <STRONG>ENVIRONMENT NAME</STRONG> in the <U>$self->{'product_name'}</U> <STRONG>PRODUCT</STRONG>...";
$environment->{'name'} = $environment_name;
($environment_id) = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $product_id);
$self->{'message'} .= "DONE.<BR />";
}
}
else {
($environment_id) = $env_id;
$self->{'message'} .= "EXISTS<BR />Importing XML Environment Failed!<BR/>";
$self->{'error'} .= "<U>$environment_name</U> <STRONG>ENVIRONMENT NAME</STRONG> already exists for the <U>$product_name</U> <STRONG>PRODUCT</STRONG>. Please use another name.";
return 0;
}
($self->{'environment_id'}) = $environment_id;
($environment->{'product_id'}) = $self->{'product_id'};
# Parse recursively through the nested child elements.
foreach my $twig_category ($root->children("category")) {
my $category_name = $twig_category->{'att'}->{'name'};
# Makes sure to get the category_id by name and product_id
my $category = Bugzilla::Testopia::Environment::Category->new({});
my ($cat_id) = $category->check_category($category_name, $product_id);
my $category_id;
# Checking if Categories already exist.
$self->{'message'} .= "..Checking if <U>$category_name</U> <STRONG>CATEGORY</STRONG> already exists...";
if ($cat_id < 1) {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U>.<BR />";
my $new_category_names = $self->{'new_category_names'};
push (@$new_category_names, $category_name);
$self->{'new_category_names'} = $new_category_names;
# Storing New Categories if Admin
if ($admin) {
$self->{'message'} .= "....Storing new <U>$category_name</U> <STRONG>CATEGORY</STRONG> in the <U>$self->{'product_name'}</U> <STRONG>PRODUCT</STRONG>...";
($category->{'product_id'}) = $product_id;
$category->{'name'} = $category_name;
($category_id) = $category->store();
$self->{'message'} .= "DONE.<BR />";
}
}
else {
($category_id) = $cat_id;
$self->{'message'} .= "EXISTS.<BR />";
}
foreach my $twig_element ($twig_category->children("element")) {
my $element = $self->parse_child_elements(1, $category_id, $category_name, $twig_element, $admin);
my $elements = $self->{'elements'};
push (@$elements, $element);
$self->{'elements'} = $elements;
}
}
if ($admin) {
$self->{'message'} .= "Finished Importing XML Environment!<BR/>";
}
else {
$self->{'message'} .= "Finished Parsing and Validating XML Environment!<BR/>";
}
}
=head2 Parse Children Elements
=head2 DESCRIPTION
Parses through elements and their children elements recursively
=cut
sub parse_child_elements() {
my ($self, $depth, $env_category_id, $category_name, $twig_element, $admin, $parent_element) = @_;
if ($depth > $self->{'max_depth'}) {
return;
}
$depth++;
my $element_name = $twig_element->{'att'}->{'name'};
# Checking if Elements already exist.
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "Checking if <U>$element_name</U> <STRONG>ELEMENT</STRONG> already exists in the <U>$category_name</U> <STRONG>CATEGORY</STRONG>...";
my ($product_id) = $self->{'product_id'};
my $element = Bugzilla::Testopia::Environment::Element->new({});
my ($elem_id) = $element->check_element($element_name, $env_category_id);
my $element_id;
if ($elem_id < 1) {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U>.<BR />";
my $new_category_elements = $self->{'new_category_elements'};
my $new_category_element = {'env_category_id' => $env_category_id, 'category_name' => $category_name, 'element_name' => $element_name};
push (@$new_category_elements, $new_category_element);
$self->{'new_category_elements'} = $new_category_elements;
# Storing New Elements if Admin
if ($admin) {
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "..Storing new <U>$element_name</U> <STRONG>ELEMENT</STRONG> in the <U>$category_name</U> <STRONG>CATEGORY</STRONG>...";
($element->{'env_category_id'}) = $env_category_id;
$element->{'name'} = $element_name;
($element->{'product_id'}) = $self->{'product_id'};
$element->{'isprivate'} = 0;
if ($parent_element) {
$element->{'parent_id'} = $parent_element->{'element_id'};
}
($element_id) = $element->store();
$self->{'message'} .= "DONE.<BR />";
}
}
else {
($element_id) = $elem_id;
$self->{'message'} .= "EXISTS.<BR />";
}
($element->{'element_id'}) = $element_id;
($element->{'env_category_id'}) = $env_category_id;
$element->{'name'} = $element_name;
($element->{'parent_id'}) = $parent_element->{'parent_id'};
my @properties;
foreach my $twig_property ($twig_element->children("property")) {
my $property_name = $twig_property->{'att'}->{'name'};
# Checking if Properties already exist.
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "....Checking if <U>$property_name</U> <STRONG>PROPERTY</STRONG> already exists...";
my $property = Bugzilla::Testopia::Environment::Property->new({});
my ($prop_id) = $property->check_property($property_name, $element_id);
my $property_id;
if ($prop_id < 1) {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U>.<BR />";
my $new_property_names = $self->{'new_property_names'};
push (@$new_property_names, $property_name);
$self->{'new_property_names'} = $new_property_names;
# Storing New Property if Admin
if ($admin) {
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "......Storing new <U>$property_name</U> <STRONG>PROPERTY</STRONG>...";
$property->{'name'} = $property_name;
($property->{'element_id'}) = $element_id;
($property_id) = $property->store();
$self->{'message'} .= "DONE.<BR />";
}
}
else {
($property_id) = $prop_id;
$self->{'message'} .= "EXISTS.<BR />";
}
$property = Bugzilla::Testopia::Environment::Property->new($property_id);
# Checking if new Selected Value and Valid Expression exist.
my $validexp;
if ($property) {
$validexp = $property->validexp();
}
my $value = $twig_property->field('value');
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "........Checking if <U>$value</U> <STRONG>VALUE</STRONG> exists in the list of selectable values...";
if ( $validexp !~ m/$value/) {
$self->{'message'} .= "<U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U>.<BR/>";
if ($admin) {
if (!defined($validexp)) {
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "..........Setting <U>$value</U> <STRONG>VALID EXPRESSION</STRONG> equal to the <STRONG>VALUE</STRONG> for the first time...";
$validexp = $value;
}
else {
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "..........Adding <U>$value</U> <STRONG>VALUE</STRONG> to the <STRONG>VALID EXPRESSION</STRONG>...";
$validexp = "$validexp | $value";
}
$property->update_property_validexp($validexp);
$self->{'message'} .= "DONE.<BR/>";
}
else {
my $new_validexp_values = $self->{'new_validexp_values'};
my $new_validexp_value = {'property_id' => $property_id, 'property_name' => $property_name, 'value' => $value};
push (@$new_validexp_values, $new_validexp_value);
$self->{'new_validexp_values'} = $new_validexp_values;
}
}
elsif (!defined($validexp)) {
$self->{'message'} .= "<STRONG>VALID EXPRESSION</STRONG> <U><FONT COLOR='RED'><STRONG>DOESN'T EXIST</STRONG></FONT></U> YET.<BR/>";
}
else {
$self->{'message'} .= "EXISTS.<BR/>";
}
if ($property_id && $admin) {
for (my $i = 1; $i < $depth; $i++) {
$self->{'message'} .= "....";
}
$self->{'message'} .= "............Storing new <STRONG>VALUE SELECTED</STRONG> <U>$value</U>...";
my $environment = Bugzilla::Testopia::Environment->new($self->{'environment_id'});
$environment->store_property_value($property_id, $element_id, $value);
$self->{'message'} .= "DONE.<BR/>";
}
$property->{'value_selected'} = $value;
push (@properties, $property);
}
my $elm_properties = $element->{'properties'};
push (@$elm_properties, @properties);
if ($parent_element) {
my $children = $parent_element->{'children'};
push (@$children, $element);
$parent_element->{'children'} = $children;
}
foreach my $twig_element_child ($twig_element->children("element")) {
$self->parse_child_elements($depth, $env_category_id, $category_name, $twig_element_child, $admin, $element);
}
return $element;
}
=head2 Checking Exists
=head2 DESCRIPTION
Checking if the Environment, Elements, Categories, and Properties already exist or not.
=cut
sub check_new_items() {
my $self = shift;
my $report;
my $new_category_names = $self->{'new_category_names'};
foreach my $new_category_name (@$new_category_names) {
$report .= "New <U>$new_category_name</U> <STRONG>CATEGORY</STRONG>.<BR/>";
}
my $new_category_elements = $self->{'new_category_elements'};
foreach my $new_category_element (@$new_category_elements) {
$report .= "New <U>$new_category_element->{'element_name'}</U> <STRONG>ELEMENT</STRONG> in the ";
if (!$new_category_element->{'env_category_id'}){
$report .= "<STRONG>new</STRONG> ";
}
$report .= "<U>$new_category_element->{'category_name'}</U> <STRONG>CATEGORY</STRONG>.<BR/>";
}
my $new_property_names = $self->{'new_property_names'};
foreach my $new_property_name (@$new_property_names) {
$report .= "New <U>$new_property_name</U> <STRONG>PROPERTY</STRONG>.<BR/>";
}
my $new_validexp_values = $self->{'new_validexp_values'};
foreach my $new_validexp_value (@$new_validexp_values) {
$report .= "New <U>$new_validexp_value->{'value'}</U> <STRONG>VALUE</STRONG> for the ";
if (!$new_validexp_value->{'property_id'}){
$report .= "<STRONG>new</STRONG> ";
}
$report .= "<U>$new_validexp_value->{'property_name'}</U> <STRONG>PROPERTY</STRONG>'s selectable value list.<BR/>";
}
return $report;
}
=head2 Store the Environment
=head2 Description
Store the Environment Name, Element-Category relationship, Element-Property relationship,
Element-ChildElement relationship, Environment-Element-Property-Value.
=cut
sub store() {
my $self = shift;
$self->{'message'} .= "Storing new XML Environment...";
if (!$self->{'environment_id'}) {
$self->{'environment_id'} = Bugzilla::Testopia::Environment->store_environment_name($self->{'name'}, $self->{'product_id'});
}
my $environment = Bugzilla::Testopia::Environment->new($self);
my $success = $environment->update();
if (!$success) {
$self->{'message'} .= "ABORTED!<BR/>";
$self->{'error'} .= "Failed to store the Environment!<BR/>";
return 0;
}
$self->{'message'} .= "DONE!<BR/>";
return 1;
}
=head2 export
=head2 Description
Exports and Environment by env_id to a scalar XML value
=cut
sub export() {
my $self = shift;
my ($env_id) = @_;
my $xml;
my $environment = Bugzilla::Testopia::Environment->new($env_id);
$xml =
"<?xml version='1.0' encoding='UTF-8'?>" .
"<!DOCTYPE environment SYSTEM 'environment.dtd' >" .
"<environment name='$environment->{'name'}' product='";
my $product = Bugzilla::Product->new($environment->{'product_id'});
$xml .= "$product->{'name'}'>";
$environment->get_environment_elements();
my $categories = {};
my $category_names = [];
my $categorized_elements = [];
my $elements = $environment->{'elements'};
my $used_elements = {};
foreach my $element (@$elements) {
my $root_element = $self->get_root_parent($element);
my $category_name = $root_element->cat_name();
$categorized_elements = $categories->{ $category_name };
for my $used_element (@$categorized_elements) {
$used_elements->{ $used_element->{'element_id'} } = 1;
}
if (!$used_elements->{$root_element->{'element_id'}}) {
push (@$categorized_elements, $root_element);
$categories->{ $category_name } = $categorized_elements;
}
}
foreach my $category_name (keys %$categories) {
$xml .= "<category name='$category_name'>";
$elements = $categories->{$category_name};
foreach my $element (@$elements) {
$element->get_children();
$xml .= $self->export_element_and_children(1, $element, $environment->{'environment_id'});
}
$xml .= "</category>";
}
return "$xml</environment>";
}
=head2 get_root_parent
=head2 Description
Helper sub that returns the root parent element in the environment of the passed in element
=cut
sub get_root_parent {
my $self = shift;
my ($element) = @_;
my $parent = $element->get_parent();
if (!$parent) {
return $element;
}
else {
$self->get_root_parent($parent);
}
}
=head2 export_element_and_children
=head2 Description
Exports Elements and their child elements to XML Recursively.
=cut
sub export_element_and_children() {
my $self = shift;
my ($depth, $element, $env_id) = @_;
if ($depth > $max_depth) {
return;
}
$depth++;
my $xml = "<element name='$element->{'name'}'>";
my $properties = $element->{'properties'};
foreach my $property (@$properties) {
my $value_selected = Bugzilla::Testopia::Environment->get_value_selected(
$env_id, $element->{'element_id'}, $property->{'property_id'});
if (defined($value_selected)) {
$xml .=
"<property name='$property->{'name'}'>" .
"<value>$value_selected</value></property>";
}
}
my $children = $element->{'children'};
foreach my $child_element (@$children) {
$child_element->get_children();
$xml .= $self->export_element_and_children($depth, $child_element, $env_id);
}
$xml .= "</element>";
return $xml;
}
1;

View File

@@ -50,16 +50,60 @@ sub builds {
my $ref = $dbh->selectcol_arrayref(
"SELECT build_id
FROM test_builds tb
JOIN test_plans tp ON tb.plan_id = tp.plan_id
WHERE tp.product_id = ?",
FROM test_builds
WHERE product_id = ?",
undef, $self->{'id'});
my @objs;
foreach my $id (@{$ref}){
push @objs, Bugzilla::Testopia::Build->new($id);
}
$self->{'build'} = \@objs;
return $self->{'build'};
$self->{'builds'} = \@objs;
return $self->{'builds'};
}
sub categories {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref(
"SELECT category_id
FROM test_case_categories
WHERE product_id = ?",
undef, $self->{'id'});
my @objs;
foreach my $id (@{$ref}){
push @objs, Bugzilla::Testopia::Category->new($id);
}
$self->{'categories'} = \@objs;
return $self->{'categories'};
}
sub environment_categories {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ref = $dbh->selectcol_arrayref(
"SELECT env_category_id
FROM test_environment_category
WHERE product_id = ?",
undef, $self->id);
my @objs;
foreach my $id (@{$ref}){
push @objs, Bugzilla::Testopia::Environment::Category->new($id);
}
$self->{'environment_categories'} = \@objs;
return $self->{'environment_categories'};
}
sub check_product_by_name {
my $self = shift;
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($used) = $dbh->selectrow_array(qq{
SELECT id
FROM products
WHERE name = ?},undef,$name);
return $used;
}
1;

View File

@@ -1,489 +0,0 @@
# -*- 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 Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Justin C. De Vries <judevries@novell.com>
package Bugzilla::Testopia::Schema;
use base qw(Bugzilla::DB::Schema);
use strict;
use Bugzilla::Error;
use Bugzilla::Util;
use Carp;
use Safe;
# Historical, needed for SCHEMA_VERSION = '1.00'
use Storable qw(dclone freeze thaw);
#New SCHEMA_Version (2.00) use this
use Data::Dumper;
use constant SCHEMA_VERSION => '2.00';
use constant ABSTRACT_SCHEMA => {
#Note: Most of types will need to be defined in a MySQL specific file some where.
test_category_templates => {
FIELDS => [
# Note: UNSMALLSERIAL might need to be added to the MySQL.pm file. Its
# definition is UNSIGNED SMALLINT AUTO_INCREMENT.
category_template_id => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
name => {TYPE => 'varchar(255)', NOTNULL => 1},
description => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
category_template_name_idx => ['name'],
],
},
test_attachments => {
FIELDS => [
# Note: Serial is a MySQL specific type. This will need to
# be written or modified to be database generic.
attachment_id => {TYPE => 'SERIAL'},
# Note: UNBIGINT need to be added to the MySQL.pm file. Its
# definition is UNSIGNED BIGINT.
plan_id => {TYPE => 'UNBIGINT'},
case_id => {TYPE => 'UNBIGINT'},
submitter_id => {TYPE => 'INT3', NOTNULL => 1},
description => {TYPE => 'MEDIUMTEXT'},
filename => {TYPE => 'MEDIUMTEXT'},
creation_ts => {TYPE => 'DATETIME'},
mime_type => {TYPE => 'varchar(100)', NOTNULL => 1},
],
INDEXES => [
attachment_plan_idx => ['plan_id'],
attachment_case_idx => ['case_id'],
],
},
test_case_categories => {
FIELDS => [
category_id => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
product_id => {TYPE => 'INT2', NOTNULL => 1},
name => {TYPE => 'varchar(240)', NOTNULL => 1},
description => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
category_name_idx => ['name'],
],
},
test_cases => {
FIELDS => [
case_id => {TYPE => 'SERIAL'},
case_status_id => {TYPE => 'INT1', NOTNULL => 1},
# Note: UNINT2 need to be added to the MySQL.pm file. Its
# definition is UNSIGNED SMALLINT.
category_id => {TYPE => 'UNINT2', NOTNULL => 1},
priority_id => {TYPE => 'INT2'},
author_id => {TYPE => 'INT3', NOTNULL => 1},
creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
isautomated => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'},
sortkey => {TYPE => 'INT4'},
script => {TYPE => 'MEDIUMTEXT'},
arguments => {TYPE => 'MEDIUMTEXT'},
summary => {TYPE => 'varchar(255)'},
requirement => {TYPE => 'varchar(255)'},
alias => {TYPE => 'varchar(255)'},
],
INDEXES => [
test_case_category_idx => ['category_id'],
test_case_author_idx => ['author_id'],
test_case_creation_date_idx => ['creation_date'],
test_case_sortkey_idx => ['sortkey'],
test_case_requirement_idx => ['requirement'],
test_case_shortname_idx => ['alias'],
],
},
test_case_bugs => {
FIELDS => [
bug_id => {TYPE => 'INT3', NOTNUTLL => 1},
case_run_id => {TYPE => 'UNBIGINT'},
],
INDEXES => [
case_run_id_idx => [qw(case_run_id bug_id)],
case_run_bug_id_idx => ['bug_id'],
],
},
test_case_runs => {
FIELDS => [
case_run_id => {TYPE => 'SERIAL'},
run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
assignee => {TYPE => 'INT3'},
testedby => {TYPE => 'INT3'},
case_run_status_id => {TYPE => 'UNINT1', NOTNULL => 1},
case_text_version => {TYPE => 'UNINT3', NOTNULL => 1},
build_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
close_date => {TYPE => 'DATETIME'},
notes => {TYPE => 'TEXT'},
iscurrent => {TYPE => 'BOOLEAN', DEFAULT => '0'},
sortkey => {TYPE => 'INT4'},
],
INDEXES => [
case_run_run_id_idx => ['run_id'],
case_run_case_id_idx => ['case_id'],
case_run_assignee_idx => ['assignee'],
case_run_testedby_idx => ['testedby'],
case_run_close_date_idx => ['close_date'],
case_run_shortkey_idx => ['sortkey'],
],
},
test_case_texts => {
FIELDS => [
case_id => {TYPE => 'UNBIGINT', NOTNULL =>1},
case_text_version => {TYPE => 'INT3', NOTNULL =>1},
who => {TYPE => 'INT3', NOTNULL =>1},
creation_ts => {TYPE => 'DATETIME', NOTNULL =>1},
action => {TYPE => 'MEDIUMTEXT'},
effect => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
case_versions_idx => {FIELDS => [qw(case_id case_text_version)],
TYPE => 'UNIQUE'},
case_versions_who_idx => ['who'],
case_versions_creation_ts_idx => ['creation_ts'],
],
},
test_case_tags => {
FIELDS => [
tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
case_id => {TYPE => 'UNBIGINT'},
],
INDEXES => [
case_tags_case_id_idx => [qw(case_id tag_id)],
case_tags_tag_id_idx => [qw(tag_id case_id)],
test_tag_name_indx => ['tag_name'],
],
},
test_plan_testers => {
FIELDS => [
tester_id => {TYPE => 'INT3', NOTNULL => 1},
product_id => {TYPE => 'INT2'},
read_only => {TYPE => 'BOOLEAN', DEFAULT => '1'},
],
},
test_tags => {
FIELDS => [
# Note: UNINT4SERIAL need to be added to the MySQL.pm file. Its
# definition is UNSIGNED INTEGER AUTO_INCREMENT.
tag_id => {TYPE => 'UNINT4SERIAL', NOTNULL => 1},
tag_name => {TYPE => 'varchar(255)', NOTNULL => 1},
],
},
test_plans => {
FIELDS => [
plan_id => {TYPE => 'SERIAL'},
product_id => {TYPE => 'INT2', NOTNULL => 1},
author_id => {TYPE => 'INT3', NOTNULL => 1},
editor_id => {TYPE => 'INT3', NOTNULL => 1},
type_id => {TYPE => 'UNINT1', NOTNULL => 1},
default_product_version => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
name => {TYPE => 'varchar(255)', NOTNULL => 1},
creation_date => {TYPE => 'DATETIME', NOTNULL => 1},
isactive => {TYPE => 'BOOLEAN', NOTNULL =>, DEFAULT => '1'},
],
INDEXES => [
plan_product_plan_id_idx => [qw(product_id plan_id)],
plan_author_idx => ['author_id'],
plan_type_idx => ['type_id'],
plan_editor_idx => ['editor_id'],
plan_isactive_idx => ['isactive'],
],
},
text_plan_text => {
FIELDS => [
plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
plan_text_version => {TYPE => 'INT4', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1},
creation_ts => {TYPE => 'DATETIME', NOTNULL => 1},
# Note: LONGTEXT needs to be added to the MySQL.pm file. Its
# definition is LONGTEXT.
plan_text => {TYPE => 'LONGTEXT'},
],
INDEXES => [
test_plan_text_version_idx => [qw(plan_id plan_text_version)],
test_plan_text_who_idx => ['who'],
],
},
test_plan_types => {
FIELDS => [
# Note: UNTINYINTSERIAL needs to be added to the MySQL.pm file. Its
# definition is UNSIGNED TINYINT AUTO_INCREMENT.
type_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
],
INDEXES => [
plan_type_name_idx => ['name'],
],
},
test_runs => {
FIELDS => [
run_id => {TYPE => 'SERIAL'},
plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
environment_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
product_version => {TYPE => 'MEDIUMTEXT'},
build_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
plan_text_version => {TYPE => 'INT4', NOTNULL => 1},
manager_id => {TYPE => 'INT3', NOTNULL => 1},
default_tester_id => {TYPE => 'INT3'},
start_date => {TYPE => 'DATETIME'},
stop_date => {TYPE => 'DATETIME'},
summary => {TYPE => 'TINYTEXT', NOTNULL => 1},
notes => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
test_run_plan_id_run_id_idx => [qw(plan_id run_id)],
test_run_manager_idx => ['manager_id'],
test_run_start_date_idx => ['start_date'],
],
},
test_case_plans => {
FIELDS => [
plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
],
INDEXES => [
case_plans_plan_id_idx => ['plan_id'],
case_plans_case_id_idx => ['plan_id'],
],
},
test_case_activity => {
FIELDS => [
case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1},
changed => {TYPE => 'DATETIME', NOTNULL => 1},
oldvalue => {TYPE => 'MEDUIMTEXT'},
newvalue => {TYPE => 'MEDUIMTEXT'},
],
INDEXES => [
case_activity_case_id_idx => ['case_id'],
case_activity_who_idx => ['who'],
case_activity_when_idx => ['changed'],
case_activity_field_idx => ['fieldid'],
],
},
test_fielddefs => {
FIELDS => [
fieldid => {TYPE => 'UNSMALLSERIAL', NOTNULL => 1},
name => {TPYE => 'varchar(100)', NOTNULL => 1},
description => {TYPE => 'MEDIUMTEXT'},
table_name => {TYPE => 'varchar(100)', NOTNULL => 1},
],
INDEXES => [
fielddefs_name_idx => ['name'],
],
},
test_plan_activity => {
FIELDS => [
plan_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1},
changed => {TYPE => 'DATETIME', NOTNULL => 1},
oldvalue => {TYPE => 'MEDIUMTEXT'},
newvalue => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
plan_activity_who_idx => ['who'],
],
},
test_case_components => {
FIELDS => [
case_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
component_id => {TYPE => 'INT2', NOTNULL => 1},
],
INDEXES => [
case_components_case_id_idx => ['case_id'],
case_commponents_component_id_idx => ['case_id'],
],
},
test_run_activity => {
FIELDS => [
run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
fieldid => {TYPE => 'UNINT2', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1},
changed => {TYPE => 'DATETIME', NOTNULL => 1},
oldvalue => {TYPE => 'MEDIUMTEXT'},
newvalue => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
run_activity_run_id_idx => ['run_id'],
run_activity_who_idx => ['who'],
run_activity_when_idx => ['changed'],
],
},
test_run_cc => {
FIELDS => [
run_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
who => {TYPE => 'INT3', NOTNULL => 1},
],
INDEXES => [
run_cc_run_id_who_idx => [qw(run_id who)],
],
},
test_email_settings => {
FIELDS => [
userid => {TYPE => 'INT3', NOTNULL => 1},
eventid => {TYPE => 'UNINT1', NOTNULL => 1},
relationship_id => {TYPE => 'UNINT1', NOTNULL => 1},
],
INDEXES => [
test_event_user_event_idx => [qw(userid eventid)],
test_event_user_relationship_idx => [qw(userid relationship_id)],
],
},
test_events => {
FIELDS => [
eventid => {TYPE => 'UNIN1', NOTNULL => 1},
name => {TYPE => 'varchar(50)'},
],
INDEXES => [
test_event_name_idx => ['name'],
],
},
test_relationships => {
FIELDS => [
relationship_id => {TYPE => 'UNINT1', NOTNULL => 1},
name => {TYPE => 'varchar(50)'},
],
},
test_case_run_status => {
FIELDS => [
case_run_status_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
name => {TYPE => 'varchar(20)'},
sortkey => {TYPE => 'INT4'},
],
INDEXES => [
case_run_status_name_idx => ['name'],
case_run_status_sortkey_idx => ['sortkey'],
],
},
test_case_status => {
FIELDS => [
case_status_id => {TYPE => 'UNTINYSERIAL', NOTNULL => 1},
name => {TYPE => 'varchar(255)'},
],
INDEXES => [
test_case_status_name_idx => ['name'],
],
},
test_case_dependencies => {
FIELDS => [
dependson => {TYPE => 'UNBIGINT', NOTNULL => 1},
blocks => {TYPE => 'UNBIGINT'},
],
},
test_case_group_map => {
FIELDS => [
case_id => {TYPE => 'UNBIGINT', NOTNULL =>1},
group_id => {TYPE => 'INT3'},
],
},
test_environments => {
FIELDS => [
environment_id => {TYPE => 'SERIAL', NOTNULL => 1},
op_sys_id => {TYPE => 'INT4'},
rep_platform_id => {TYPE => 'INT4'},
name => {TYPE => 'varchar(255)'},
xml => {TYPE => 'MEDIUMTEXT'},
],
INDEXES => [
environment_op_sys_idx => ['op_sys_id'],
environment_platform_idx => ['rep_platform_id'],
environment_name_idx => ['name'],
],
},
test_run_tags => {
FIELDS => [
tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
run_id => {TYPE => 'UNBIGINT'},
],
INDEXES => [
run_tags_idx => ['qw(tag_id, run_id'],
],
},
test_plan_tags => {
FIELDS => [
tag_id => {TYPE => 'UNINT4', NOTNULL => 1},
plan_id => {TYPE => 'UNBIGINT'},
],
INDEXES => [
plan_tags_idx => ['qw(tag_id, plan_id'],
],
},
test_build => {
FIELDS => [
build_id => {TYPE => 'SERIAL', NOTNULL => 1},
product_id => {TYPE => 'INT2', NOTNULL => 1},
name => {TYPE => 'varchar(255)'},
description => {TYPE => 'TEXT'},
],
},
test_attachment_data => {
FIELDS => [
attachment_id => {TYPE => 'UNBIGINT', NOTNULL => 1},
contents => {TYPE => 'LONGBLOB'},
],
},
test_named_queries => {
FIELDS => [
userid => {TYPE => 'INT3', NOTNULL => 1},
name => {TYPE => 'varchar(64)', NOTNULL => 1},
isvisible => {TYEP => 'BOOLEAN', NOTNULL => 1, DEFAULT => 1},
query => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
],
INDEXES => [
test_namedquery_name_idx => ['name'],
],
},
};
1;

View File

@@ -133,9 +133,10 @@ sub init {
my $type;
my $obj = trim($cgi->param('current_tab')) || ThrowUserError('testopia-missing-parameter', {'param' => 'current_tab'});
ThrowUserError('unknown-tab') if $obj !~ '^(case|plan|run|case_run)$';
ThrowUserError('unknown-tab') if $obj !~ '^(case|plan|run|case_run|environment)$';
trick_taint($obj);
# Set up tables for field sort order
my $order = $cgi->param('order') || '';
if ($order eq 'author') {
push @supptables, "INNER JOIN profiles as map_author ON map_author.userid = test_". $obj ."s.author_id";
@@ -158,7 +159,7 @@ sub init {
push @orderby, 'map_tester.login_name';
}
elsif ($order eq 'product') {
push @supptables, "INNER JOIN products ON products.id = test_". $obj ."s.product_id";
push @supptables, "LEFT JOIN products ON products.id = test_". $obj ."s.product_id";
push @orderby, 'products.name';
}
elsif ($order eq 'build') {
@@ -178,6 +179,9 @@ sub init {
push @orderby, 'versions.value';
}
elsif ($order eq 'priority') {
if ($obj eq 'case_run'){
push @supptables, "INNER JOIN test_cases ON test_cases.case_id = test_case_runs.case_id";
}
push @supptables, "INNER JOIN priority ON priority.id = test_cases.priority_id";
push @orderby, 'test_cases.priority_id';
}
@@ -201,7 +205,13 @@ sub init {
push @orderby, 'case_status.name';
}
elsif ($order eq 'summary') {
push @orderby, 'test_'. $obj .'s.summary';
if ($obj eq 'case_run'){
push @supptables, "INNER JOIN test_cases AS cases ON cases.case_id = test_case_runs.case_id";
push @orderby, 'cases.summary';
}
else{
push @orderby, 'test_'. $obj .'s.summary';
}
}
elsif ($order eq 'created') {
push @orderby, 'test_'. $obj .'s.creation_date';
@@ -215,6 +225,7 @@ sub init {
push @orderby, 'test_'. $obj .'s.' . $order;
}
}
my @funcdefs =
(
"^category," => sub {
@@ -250,12 +261,71 @@ sub init {
$f = "plan_texts.plan_text";
},
"^environment," => sub {
if ($obj eq 'case_run'){
$f = "environment_id";
}
else{
push(@supptables,
"LEFT JOIN test_environments AS env " .
"ON test_runs.environment_id = env.environment_id");
$f = "env.xml";
}
},
"^env_products," => sub {
print STDERR "THIS IS HERE";
push(@supptables,
"LEFT JOIN test_environments AS env " .
"ON test_runs.environment_id = env.environment_id");
$f = "env.xml";
"INNER JOIN products as env_products
ON test_environments.product_id = env_products.id");
$f = 'env_products.id'
},
"^env_categories," => sub {
push(@supptables,
"INNER JOIN test_environment_map as env_map_categories
ON test_environments.environment_id = env_map_categories.environment_id");
push(@supptables,
"INNER JOIN test_environment_element as env_element
ON env_map_categories.element_id = env_element.element_id");
push(@supptables,
"INNER JOIN test_environment_category as env_categories
ON env_element.env_category_id = env_categories.env_category_id");
$f = 'env_categories.env_category_id'
},
"^env_elements," => sub {
push(@supptables,
"INNER JOIN test_environment_map as env_map_elements
ON test_environments.environment_id = env_map_elements.environment_id");
push(@supptables,
"INNER JOIN test_environment_element as env_element
ON env_map_elements.element_id = env_element.element_id");
$f = 'env_element.element_id'
},
"^env_properties," => sub {
push(@supptables,
"INNER JOIN test_environment_map as env_map_properties
ON test_environments.environment_id = env_map_properties.environment_id");
push(@supptables,
"INNER JOIN test_environment_property as env_property
ON env_map_properties.property_id = env_property.property_id");
$f = 'env_property.property_id'
},
"^env_expressions," => sub {
push(@supptables,
"INNER JOIN test_environment_map as env_map_value
ON test_environments.environment_id = env_map_value.environment_id");
$f = 'env_map_value.value_selected'
},
"^env_value_selected," => sub {
push(@supptables,
"INNER JOIN test_environment_map as env_map_value_selected
ON test_environments.environment_id = env_map_value_selected.environment_id");
$f = 'env_map_value_selected.value_selected'
},
"^component," => sub {
if ($obj eq 'case_run'){
push(@supptables,
"INNER JOIN test_cases
ON test_cases.case_id = test_case_runs.case_id");
}
push(@supptables,
"INNER JOIN test_case_components AS tc_components " .
"ON test_cases.case_id = tc_components.case_id");
@@ -264,6 +334,22 @@ sub init {
"ON components.id = tc_components.component_id");
$f = "components.name";
},
"^priority_id," => sub {
if ($obj eq 'case_run'){
push(@supptables,
"INNER JOIN test_cases
ON test_cases.case_id = test_case_runs.case_id");
}
$f = "test_cases.priority_id";
},
"^isautomated," => sub {
if ($obj eq 'case_run'){
push(@supptables,
"INNER JOIN test_cases
ON test_cases.case_id = test_case_runs.case_id");
}
$f = "test_cases.isautomated";
},
"^milestone," => sub {
push(@supptables,
"INNER JOIN test_builds AS builds " .
@@ -282,13 +368,33 @@ sub init {
"ON case_bugs.bug_id = bugs.bug_id");
$f = "bugs.bug_id";
},
"^case_summary," => sub {
push(@supptables,
"INNER JOIN test_cases AS cases " .
"ON cases.case_id = test_case_runs.case_id");
$f = "cases.summary";
},
"^tags," => sub {
push(@supptables,
"INNER JOIN test_". $obj ."_tags AS ". $obj ."_tags " .
"ON test_". $obj ."s.". $obj ."_id = ". $obj ."_tags.". $obj ."_id");
push(@supptables,
"INNER JOIN test_tags " .
"ON ". $obj ."_tags.tag_id = test_tags.tag_id");
if ($obj eq 'case_run'){
push(@supptables,
"INNER JOIN test_cases " .
"ON test_case_runs.case_id = test_cases.case_id");
push(@supptables,
"INNER JOIN test_case_tags AS case_tags " .
"ON test_cases.case_id = case_tags.case_id");
push(@supptables,
"INNER JOIN test_tags " .
"ON case_tags.tag_id = test_tags.tag_id");
}
else{
push(@supptables,
"INNER JOIN test_". $obj ."_tags AS ". $obj ."_tags " .
"ON test_". $obj ."s.". $obj ."_id = ". $obj ."_tags.". $obj ."_id");
push(@supptables,
"INNER JOIN test_tags " .
"ON ". $obj ."_tags.tag_id = test_tags.tag_id");
}
$f = "test_tags.tag_name";
},
"^case_plan_id," => sub {
@@ -324,17 +430,28 @@ sub init {
"ON test_plans.product_id = products.id");
$f = "test_plans.product_id";
},
"^(author|editor|manager|default_tester)," => sub {
"^(author|manager|default_tester)," => sub {
push(@supptables,
"INNER JOIN profiles AS map_$1 " .
"ON test_". $obj ."s.". $1 ."_id = map_$1.userid");
$f = "map_$1.login_name";
$f = "map_$1.login_name";
},
"^(assignee|testedby)," => sub {
push(@supptables,
if ($obj eq 'run'){
push(@supptables,
"LEFT JOIN test_case_runs AS case_run " .
"ON case_run.run_id = test_runs.run_id");
push(@supptables,
"INNER JOIN profiles AS map_$1 " .
"ON case_run.". $1 ." = map_$1.userid");
}
else {
push(@supptables,
"INNER JOIN profiles AS map_$1 " .
"ON test_". $obj ."s.". $1 ." = map_$1.userid");
}
$f = "map_$1.login_name";
},
",isnotnull" => sub {
$term = "$ff is not null";
@@ -409,22 +526,46 @@ sub init {
if ($cgi->param('case_id')) {
my $type = "anyexact";
if ($cgi->param('caseidtype') && $cgi->param('caseidtype') eq 'exclude') {
$type = "nowords";
if ($cgi->param('caseidtype'))
{
if ($cgi->param('caseidtype') eq 'exclude')
{
$type = "nowords";
}
else
{
$type = $cgi->param('caseidtype')
}
}
push(@specialchart, ["case_id", $type, join(',', $cgi->param('case_id'))]);
}
if ($cgi->param('run_id')) {
my $type = "anyexact";
if ($cgi->param('runidtype') && $cgi->param('runidtype') eq 'exclude') {
$type = "nowords";
if ($cgi->param('runidtype'))
{
if ($cgi->param('runidtype') eq 'exclude')
{
$type = "nowords";
}
else
{
$type = $cgi->param('runidtype')
}
}
push(@specialchart, ["run_id", $type, join(',', $cgi->param('run_id'))]);
}
if ($cgi->param('plan_id')) {
my $type = "anyexact";
if ($cgi->param('planidtype') && $cgi->param('planidtype') eq 'exclude') {
$type = "nowords";
if ($cgi->param('planidtype'))
{
if ($cgi->param('planidtype') eq 'exclude')
{
$type = "nowords";
}
else
{
$type = $cgi->param('planidtype')
}
}
if ($obj eq 'case'){
push(@specialchart, ["case_plan_id", $type, join(',', $cgi->param('plan_id'))]);
@@ -469,7 +610,8 @@ sub init {
my @legal_fields = ("case_status_id", "category", "priority_id",
"component", "isautomated", "case_run_status_id",
"default_product_version", "type_id",
"build", "environment_id", "milestone");
"build", "environment_id", "milestone", "env_products",
"env_categories", "env_elements", "env_properties", "env_expressions");
foreach my $field ($cgi->param()) {
if (lsearch(\@legal_fields, $field) != -1) {
@@ -509,7 +651,7 @@ sub init {
}
# Check for author
my @clist;
foreach my $profile ("author", "editor", "manager", "default_tester",
foreach my $profile ("author", "manager", "default_tester",
"assignee", "testedby"){
$t = $cgi->param($profile . "_type") || '';
if ($t eq "exact") {
@@ -535,9 +677,9 @@ sub init {
}
# check static text fields
foreach my $f ("summary", "tcaction", "tceffect", "script",
foreach my $f ("case_summary", "summary", "tcaction", "tceffect", "script",
"requirement", "name", "plan_text", "environment",
"notes") {
"notes", "env_value_selected") {
if (defined $cgi->param($f)) {
my $s = trim($cgi->param($f));
if ($s ne "") {
@@ -557,6 +699,7 @@ sub init {
push @wherepart, 'test_case_runs.iscurrent = 1';
}
}
my @funcnames;
while (@funcdefs) {
my $key = shift(@funcdefs);

View File

@@ -31,7 +31,7 @@ arguments:
=over
=item type - one of 'case', 'plan', 'run', or 'caserun'
=item type - one of 'case', 'plan', 'run', 'caserun', or 'environment'
=item url - the cgi file that is calling this
@@ -135,6 +135,9 @@ sub init {
elsif ($type eq 'case_run'){
$o = Bugzilla::Testopia::TestCaseRun->new($id);
}
elsif ($type eq 'environment'){
$o = Bugzilla::Testopia::Environment->new($id);
}
push (@ids, $id);
push (@list, $o);
}
@@ -248,7 +251,7 @@ sub save_list {
(join(",", $self->{'id_list'}), $self->{'user'}->id, "__". $self->{'type'} ."__"));
}
else {
$dbh->do("INSERT INTO test_named_queries VALUES(?,?,?,?)", undef,
$dbh->do("INSERT INTO test_named_queries (userid, name, isvisible, query) VALUES(?,?,?,?)", undef,
($self->{'user'}->id, "__". $self->{'type'} ."__", 0, join(",", $self->{'id_list'})));
}
$dbh->bz_unlock_tables();
@@ -344,8 +347,10 @@ sub get_next{
###############################
sub list { return $_[0]->{'list'}; }
sub id_list { return $_[0]->{'id_list'}; }
sub list_count { return $_[0]->{'list_count'}; }
sub page { return $_[0]->{'page'}; }
sub url_loc { return $_[0]->{'url_loc'}; }
=head2 page_size
@@ -405,6 +410,21 @@ sub get_url {
return $self->{'url'};
}
sub get_query_part {
my $self = shift;
my $cgi = $self->{'cgi'};
my @keys = $cgi->param;
my $qstring;
foreach my $key (@keys){
my @vals = $cgi->param($key);
foreach my $val (@vals){
$qstring .= $key ."=". $val ."&";
}
}
chop $qstring;
return $qstring
}
=head2 page_count
Returns a total count of the number of pages returned by a query.

View File

@@ -24,7 +24,7 @@
# Christopher Aillon <christopher@aillon.com>
# Myk Melez <myk@mozilla.org>
# Jeff Hedlund <jeff.hedlund@matrixsi.com>
# Fr<EFBFBD><EFBFBD>ic Buclin <LpSolit@gmail.com>
# Frederic Buclin <LpSolit@gmail.com>
#
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
@@ -193,41 +193,26 @@ sub get_selectable_components {
my $self = shift;
my ($byid) = @_;
my $dbh = Bugzilla->dbh;
my @compids;
my @exclusions;
unless ($byid) {
foreach my $e (@{$self->components}){
push @exclusions, $e->{'id'};
}
}
my $query = "SELECT id FROM components
WHERE product_id = ?";
my $query = "SELECT DISTINCT id FROM components
WHERE product_id IN (" . join(",", @{$self->get_product_ids}) . ") ";
if (@exclusions){
$query .= " AND id NOT IN(". join(",", @exclusions) .")";
$query .= "AND id NOT IN(". join(",", @exclusions) .") ";
}
foreach my $p (@{$self->plans}){
my $ref = $dbh->selectcol_arrayref(
$query, undef, $p->{'product_id'});
push @compids, @{$ref};
}
my %compseen;
foreach (@compids){
$compseen{$_} = 1;
}
@compids = keys %compseen;
#TODO: 2.22 use Bugzilla::Component
$query .= "ORDER BY name";
my $ref = $dbh->selectcol_arrayref($query);
my @comps;
push @comps, {'id' => '0', 'name' => '--Please Select--'} unless $byid;
foreach my $id (@compids){
my $ref = $dbh->selectrow_hashref(
"SELECT id, name FROM components
WHERE id = ?", undef, $id);
push @comps, $ref;
#push @comps, Bugzilla::Component->new($id);
foreach my $id (@$ref){
push @comps, Bugzilla::Component->new($id);
}
return \@comps;
}
=head2 get_available_components
@@ -275,35 +260,25 @@ plans referenced by this case.
#TODO: Move to Testopia::Product.pm in 2.22
sub get_category_list{
my $self = shift;
my ($by_name) = @_;
my $dbh = Bugzilla->dbh;
my @categories;
my @product_ids;
foreach my $p (@{$self->plans}){
if (Bugzilla::Testopia::Util::can_view_product($p->product_id)){
push @categories, @{$p->categories};
}
push @product_ids, $p->product_id;
}
my %seen;
foreach my $c (@categories){
$seen{$c->id} = $c;
my $id_part;
my $name_part;
if ($by_name){
$id_part = "cat.name AS id";
$name_part = ", cat.name as name";
}
@categories = values %seen;
@categories = sort @categories;
return \@categories;
}
=head2 get_distinct_categories
Returns a list of all user viewable categories for use in searches
=cut
#TODO: Move to Testopia::Product.pm in 2.22
sub get_distinct_categories{
my $self = shift;
my $dbh = Bugzilla->dbh;
my $query = "SELECT DISTINCT cat.name AS id, cat.name " .
"FROM test_case_categories AS cat " .
"JOIN products ON cat.product_id = products.id " .
else {
$id_part = "category_id";
}
my $query = "SELECT DISTINCT $id_part $name_part ";
$query .= "FROM test_case_categories AS cat " .
"JOIN products ON cat.product_id = products.id " .
"LEFT JOIN group_control_map " .
"ON group_control_map.product_id = products.id ";
if (Param('useentrygroupdefault')) {
@@ -316,11 +291,50 @@ sub get_distinct_categories{
$query .= "AND group_id NOT IN(" .
join(',', values(%{Bugzilla->user->groups})) . ") ";
}
$query .= "WHERE group_id IS NULL ORDER BY cat.name";
my $ref = $dbh->selectall_arrayref($query, {'Slice'=>{}});
$query .= "WHERE group_id IS NULL ";
$query .= "AND cat.product_id IN (". join(",", @product_ids) . ") " unless ($by_name || !@product_ids);
$query .= "ORDER BY cat.name";
return $ref;
if ($by_name){
my $ref = $dbh->selectall_arrayref($query, {'Slice'=>{}});
return $ref;
}
my $ids = $dbh->selectcol_arrayref($query);
my @categories;
foreach my $c (@$ids){
push @categories, Bugzilla::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
@@ -373,8 +387,8 @@ sub add_tag {
$dbh->bz_unlock_tables();
return 1;
}
$dbh->do("INSERT INTO test_case_tags(tag_id, case_id) VALUES(?,?)",
undef, $tag_id, $self->{'case_id'});
$dbh->do("INSERT INTO test_case_tags(tag_id, case_id, userid) VALUES(?,?,?)",
undef, $tag_id, $self->{'case_id'}, Bugzilla->user->id);
$dbh->bz_unlock_tables();
return 0;
@@ -396,6 +410,51 @@ sub remove_tag {
return;
}
=head2 attach_bug
Attaches the specified bug to this test case
=cut
sub attach_bug {
my $self = shift;
my ($bug, $case_id) = @_;
$case_id ||= $self->{'case_id'};
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_case_bugs WRITE');
my ($exists) = $dbh->selectrow_array(
"SELECT bug_id
FROM test_case_bugs
WHERE case_id=?
AND bug_id=?",
undef, ($case_id, $bug));
if ($exists) {
$dbh->bz_unlock_tables();
return;
}
$dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id, case_id)
VALUES(?,?,?)", undef, ($bug, undef, $self->{'case_id'}));
$dbh->bz_unlock_tables();
}
=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;
$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
@@ -465,14 +524,17 @@ text has changed and thus requiring a new version be created.
sub diff_case_doc {
my $self = shift;
my ($newaction, $neweffect) = @_;
my ($newaction, $neweffect, $newsetup, $newbreakdown) = @_;
my $dbh = Bugzilla->dbh;
my ($oldaction, $oldeffect) = $dbh->selectrow_array(
"SELECT action, effect FROM test_case_texts
WHERE case_id = ? AND case_text_version = ?",
undef, $self->{'case_id'}, $self->version);
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
}
@@ -516,7 +578,8 @@ sub store {
my $key = $dbh->bz_last_key( 'test_cases', 'case_id' );
$self->store_text($key, $self->{'author_id'}, $self->{'action'}, $self->{'effect'}, $timestamp);
$self->store_text($key, $self->{'author_id'}, $self->{'action'}, $self->{'effect'},
$self->{'setup'}, $self->{'breakdown'}, $timestamp);
$self->update_deps($self->{'dependson'}, $self->{'blocks'}, $key);
foreach my $p (@{$self->{'plans'}}){
$self->link_plan($p->id, $key);
@@ -535,16 +598,16 @@ author id, action text, effect text, and an optional timestamp.
sub store_text {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($key, $author, $action, $effect, $timestamp) = @_;
my ($key, $author, $action, $effect, $setup, $breakdown, $timestamp) = @_;
if (!defined $timestamp){
($timestamp) = Bugzilla::Testopia::Util::get_time_stamp();
}
trick_taint($action);
trick_taint($effect);
my $version = $self->version || 0;
$dbh->do("INSERT INTO test_case_texts VALUES(?,?,?,?,?,?)",
$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);
$timestamp, $action, $effect, $setup, $breakdown);
$self->{'version'} = $version;
return $self->{'version'};
@@ -579,6 +642,43 @@ sub link_plan {
$dbh->do("INSERT INTO test_case_plans (plan_id, case_id)
VALUES (?,?)", undef, $plan_id, $case_id);
$dbh->bz_unlock_tables();
# Update the plans array to include new plan added.
push @{$self->{'plans'}}, Bugzilla::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) = @_;
$dbh->bz_lock_tables('test_case_plans WRITE', 'test_case_runs WRITE',
'test_runs READ', 'test_plans READ');
if (!$self->can_unlink_plan($plan_id)){
$dbh->bz_unlock_tables();
return 0;
}
$dbh->do("DELETE FROM test_case_plans
WHERE plan_id = ?
AND case_id = ?", undef, $plan_id, $self->{'case_id'});
$dbh->bz_unlock_tables();
# Update the plans array.
delete $self->{'plans'};
return 1;
}
=head2 copy
@@ -604,7 +704,9 @@ sub copy {
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'}, $timestamp);
$self->store_text($key, Bugzilla->user->id, $self->text->{'action'},
$self->text->{'effect'}, $self->text->{'setup'},
$self->text->{'breakdown'}, $timestamp);
}
return $key;
@@ -621,15 +723,37 @@ sub check_alias {
my $self = shift;
my ($alias) = @_;
return unless $alias;
my $id = $self->{'case_id'} || '';
my $dbh = Bugzilla->dbh;
($id) = $dbh->selectrow_array(
"SELECT case_id
FROM test_cases
WHERE alias = ?
AND case_id != ?",
undef, ($alias, $id));
return $id;
}
=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 = ?
AND case_id != ?",
undef, ($alias, $self->{'case_id'}));
WHERE alias = ?",
undef, ($alias));
return $id;
}
@@ -662,6 +786,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_cases");
$dbh->do("INSERT INTO test_case_activity
(case_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, $self->{'case_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field});
@@ -711,6 +836,21 @@ sub history {
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;
}
=head2 lookup_status
Takes an ID of the status field and returns the value
@@ -764,6 +904,23 @@ sub lookup_category {
return $value;
}
=head2 lookup_category_by_name
Returns the id of the category name passed.
=cut
sub lookup_category_by_name {
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($value) = $dbh->selectrow_array(
"SELECT category_id
FROM test_case_categories
WHERE name = ?",
undef, $name);
return $value;
}
=head2 lookup_priority
Takes an ID of the priority field and returns the value
@@ -782,6 +939,24 @@ sub lookup_priority {
return $value;
}
=head2 lookup_priority_by_name
Returns the id of the priority name passed.
=cut
sub lookup_priority_by_value {
my ($value) = @_;
my $dbh = Bugzilla->dbh;
my ($id) = $dbh->selectrow_array(
"SELECT id
FROM priority
WHERE value = ?",
undef, $value);
return $id;
}
=head2 lookup_default_tester
Takes an ID of the default_tester field and returns the value
@@ -991,11 +1166,37 @@ sub _generate_dep_tree {
my $deps = _get_dep_lists("dependson", "blocked", $case_id);
return unless scalar @$deps;
foreach my $id (@$deps){
#print "ID $id";
$self->_generate_dep_tree($id);
push @{$self->{'dep_list'}}, $id
}
}
=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) = @_;
return 0 if (!UserInGroup('managetestplans'));
return 0 if scalar @{$self->plans} < 2;
my $dbh = Bugzilla->dbh;
my ($res) = $dbh->selectrow_array(
"SELECT 1
FROM test_case_runs
INNER JOIN test_runs ON test_case_runs.run_id = test_runs.run_id
WHERE test_runs.plan_id = ?
AND test_case_runs.case_id = ?",
undef, ($plan_id, $self->{'case_id'}));
return !$res;
}
=head2 canedit
Returns true if the logged in user has rights to edit this test case.
@@ -1221,16 +1422,19 @@ sub components {
my $dbh = Bugzilla->dbh;
return $self->{'components'} if exists $self->{'components'};
#TODO: 2.22 use Bugzilla::Component
my $comps = $dbh->selectall_arrayref(
"SELECT comp.id, comp.name
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'});
$self->{'components'} = $comps;
my @comps;
foreach my $id (@$comps){
push @comps, Bugzilla::Component->new($id);
}
$self->{'components'} = \@comps;
return $self->{'components'};
}
@@ -1293,8 +1497,7 @@ sub bugs {
return $self->{'bugs'} if exists $self->{'bugs'};
my $ref = $dbh->selectcol_arrayref(
"SELECT DISTINCT bug_id
FROM test_case_bugs b
JOIN test_case_runs r ON r.case_run_id = b.case_run_id
FROM test_case_bugs
WHERE case_id = ?",
undef, $self->{'case_id'});
my @bugs;
@@ -1317,11 +1520,12 @@ sub text {
my $self = shift;
my $dbh = Bugzilla->dbh;
return $self->{'text'} if exists $self->{'text'};
my $text = $dbh->selectrow_hashref("SELECT action, effect
FROM test_case_texts
WHERE case_id=? AND case_text_version=?",
undef, $self->{'case_id'},
$self->version);
my $text = $dbh->selectrow_hashref(
"SELECT action, effect, setup, breakdown, who author_id, case_text_version AS version
FROM test_case_texts
WHERE case_id=? AND case_text_version=?",
undef, $self->{'case_id'},
$self->version);
$self->{'text'} = $text;
return $self->{'text'};
}
@@ -1409,7 +1613,21 @@ sub blocked_list {
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
@@ -1439,6 +1657,20 @@ sub dependson_list {
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)
}
=head1 TODO

View File

@@ -50,6 +50,7 @@ use Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::Constants;
use Date::Format;
###############################
#### Initialization ####
@@ -81,6 +82,7 @@ use constant DB_COLUMNS => qw(
test_case_runs.case_run_status_id
test_case_runs.case_text_version
test_case_runs.build_id
test_case_runs.environment_id
test_case_runs.notes
test_case_runs.close_date
test_case_runs.iscurrent
@@ -161,10 +163,11 @@ sub store {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO test_case_runs ($columns)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", undef,
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", undef,
(undef, $self->{'run_id'}, $self->{'case_id'}, $self->{'assignee'},
undef, IDLE, $self->{'case_text_version'},
$self->{'build_id'}, $self->{'notes'}, undef, 1, 0));
$self->{'build_id'}, $self->{'environment_id'},
undef, undef, 1, 0));
my $key = $dbh->bz_last_key( 'test_case_runs', 'case_run_id' );
return $key;
@@ -180,7 +183,10 @@ sub clone {
my $self = shift;
my ($fields) = @_;
my $dbh = Bugzilla->dbh;
$self->{'build_id'} = $fields->{'build_id'};
my $note = "Build or Environment changed. Resetting to IDLE.";
$self->append_note($note);
$self->{'build_id'} = $fields->{'build_id'} if $fields->{'build_id'};
$self->{'environment_id'} = $fields->{'environment_id'} if $fields->{'environment_id'};
my $entry = $self->store;
$self->set_as_current($entry);
return $entry;
@@ -188,28 +194,54 @@ sub clone {
=head2 check_exists
Checks for an existing entry with the same build for this case and run
and returns the id if it is found
Checks for an existing entry with the same build and environment for this
case and run and returns the id if it is found
=cut
sub check_exists {
my $self = shift;
my ($run_id, $case_id, $build_id) = @_;
my ($run_id, $case_id, $build_id, $env_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));
return $is;
}
=head2 lookup_case_run_id
Checks for an existing entry based on run, case, and build,
and returns the id if it is found
=cut
sub lookup_case_run_id
{
my ($run_id, $case_id, $build_id) = @_;
my $dbh = Bugzilla->dbh;
my ($case_run_id) = $dbh->selectrow_array(
"SELECT case_run_id
FROM test_case_runs
WHERE run_id = ? AND case_id = ? AND build_id = ?",
undef, ($run_id, $case_id, $build_id));
return $is;
return $case_run_id;
}
=head2 update
@@ -227,9 +259,9 @@ sub update {
if ($self->is_closed_status($fields->{'case_run_status_id'})){
$fields->{'close_date'} = Bugzilla::Testopia::Util::get_time_stamp();
}
my ($is) = $self->check_exists($self->run_id, $self->case_id, $fields->{'build_id'});
my ($is) = $self->check_exists($self->run_id, $self->case_id, $fields->{'build_id'}, $fields->{'environment_id'});
if ($fields->{'build_id'} != $self->{'build_id'}){
if ($fields->{'build_id'} != $self->{'build_id'} || $fields->{'environment_id'} != $self->{'environment_id'}){
if ($is){
return $is;
}
@@ -311,6 +343,19 @@ sub set_build {
$self->{'build_id'} = $build_id;
}
=head2 set_environment
Sets the environment on a case-run
=cut
sub set_environment {
my $self = shift;
my ($env_id) = @_;
$self->_update_fields({'environment_id' => $env_id});
$self->{'environment_id'} = $env_id;
}
=head2 set_status
Sets the status on a case-run and updates the close_date and testedby
@@ -356,17 +401,56 @@ sub set_assignee {
$self->_update_fields({'assignee' => $user_id});
}
=head2 set_note
=head2 lookup_status
Returns the status name of the given case_run_status_id
=cut
sub lookup_status {
my $self = shift;
my ($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;
}
=head2 append_note
Updates the notes field for the case-run
=cut
sub set_note {
sub append_note {
my $self = shift;
my ($note, $append) = @_;
if ($append){
my $note = $self->notes . "\n" . $note;
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;
@@ -456,18 +540,35 @@ sub attach_bug {
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('test_case_bugs WRITE');
my ($is) = $dbh->selectrow_array(
my ($exists) = $dbh->selectrow_array(
"SELECT bug_id
FROM test_case_bugs
WHERE case_run_id=?
AND bug_id=?",
undef, ($caserun_id, $bug));
if ($is) {
if ($exists) {
$dbh->bz_unlock_tables();
return;
}
$dbh->do("INSERT INTO test_case_bugs (bug_id, case_run_id)
VALUES(?,?)", undef, ($bug, $self->{'case_run_id'}));
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_unlock_tables();
}
@@ -569,7 +670,6 @@ 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 notes { return $_[0]->{'notes'}; }
sub close_date { return $_[0]->{'close_date'}; }
sub iscurrent { return $_[0]->{'iscurrent'}; }
sub status_id { return $_[0]->{'case_run_status_id'}; }
@@ -577,6 +677,25 @@ sub sortkey { return $_[0]->{'sortkey'}; }
sub isprivate { return $_[0]->{'isprivate'}; }
sub updated_deps { return $_[0]->{'updated_deps'}; }
=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
@@ -618,6 +737,19 @@ sub build {
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
@@ -817,7 +949,17 @@ Returns true if the logged in user has rights to delete this case-run.
sub candelete {
my $self = shift;
return $self->canedit && Param('allow-test-deletion');
return ($self->canedit
&& Param('allow-test-deletion')
&& scalar @{$self->get_case_run_list} == 1
&& $self->status eq 'IDLE');
}
sub do_delete {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->do("DELETE FROM test_case_runs WHERE case_run_id = ?", undef, $self->id);
}
=head1 SEE ALSO

View File

@@ -66,7 +66,6 @@ use base qw(Exporter);
plan_id
product_id
author_id
editor_id
type_id
default_product_version
name
@@ -79,7 +78,6 @@ use constant DB_COLUMNS => qw(
test_plans.plan_id
test_plans.product_id
test_plans.author_id
test_plans.editor_id
test_plans.type_id
test_plans.default_product_version
test_plans.name
@@ -158,13 +156,13 @@ sub store {
my $dbh = Bugzilla->dbh;
my $timestamp = Bugzilla::Testopia::Util::get_time_stamp();
$dbh->do("INSERT INTO test_plans ($columns)
VALUES (?,?,?,?,?,?,?,?,?)",
VALUES (?,?,?,?,?,?,?,?)",
undef, (undef, $self->{'product_id'}, $self->{'author_id'},
$self->{'editor_id'}, $self->{'type_id'},
$self->{'default_product_version'}, $self->{'name'},
$self->{'type_id'}, $self->{'default_product_version'}, $self->{'name'},
$timestamp, 1));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
$self->store_text($key, $self->{'author_id'}, $self->text, $timestamp);
$self->{'plan_id'} = $key;
return $key;
}
@@ -186,7 +184,9 @@ sub store_text {
$text ||= '';
trick_taint($text);
my $version = $self->version || 0;
$dbh->do("INSERT INTO test_plan_texts VALUES(?,?,?,?,?)",
$dbh->do("INSERT INTO test_plan_texts
(plan_id, plan_text_version, who, creation_ts, plan_text)
VALUES(?,?,?,?,?)",
undef, $key, ++$version, $author,
$timestamp, $text);
$self->{'version'} = $version;
@@ -208,10 +208,9 @@ sub clone {
my $dbh = Bugzilla->dbh;
my ($timestamp) = Bugzilla::Testopia::Util::get_time_stamp();
$dbh->do("INSERT INTO test_plans ($columns)
VALUES (?,?,?,?,?,?,?,?,?)",
VALUES (?,?,?,?,?,?,?,?)",
undef, (undef, $self->{'product_id'}, $self->{'author_id'},
$self->{'editor_id'}, $self->{'type_id'},
$self->{'default_product_version'}, $name,
$self->{'type_id'}, $self->{'default_product_version'}, $name,
$timestamp, 1));
my $key = $dbh->bz_last_key( 'test_plans', 'plan_id' );
if ($store_doc){
@@ -241,6 +240,7 @@ sub toggle_archive {
WHERE plan_id = ?", undef, $newvalue, $self->{'plan_id'});
my $field_id = Bugzilla::Testopia::Util::get_field_id("isactive", "test_plans");
$dbh->do("INSERT INTO test_plan_activity
(plan_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'plan_id'}, $field_id, Bugzilla->user->id,
$timestamp, $oldvalue, $newvalue));
@@ -270,8 +270,8 @@ sub add_tag {
$dbh->bz_unlock_tables();
return 1;
}
$dbh->do("INSERT INTO test_plan_tags VALUES(?,?)",
undef, ($tag_id, $self->{'plan_id'}));
$dbh->do("INSERT INTO test_plan_tags(tag_id, plan_id, userid) VALUES(?,?,?)",
undef, ($tag_id, $self->{'plan_id'}), Bugzilla->user->id);
$dbh->bz_unlock_tables();
}
@@ -450,6 +450,103 @@ sub get_plan_types {
ORDER BY name",
{"Slice"=>{}});
return $types;
}
=head2 last_changed
Returns the date of the last change in the history table
=cut
sub last_changed {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($date) = $dbh->selectrow_array(
"SELECT MAX(changed)
FROM test_plan_activity
WHERE plan_id = ?",
undef, $self->id);
return $self->{'creation_date'} unless $date;
return $date;
}
=head2 lookup_plan_type
Returns a type name matching the given type id
=cut
sub lookup_plan_type {
my $self = shift;
my $type_id = shift;
my $dbh = Bugzilla->dbh;
my $type = $dbh->selectrow_hashref(
"SELECT type_id AS id, name
FROM test_plan_types
WHERE type_id = ?",
undef, $type_id);
return $type;
}
=head2 check_plan_type
Returns true if a type with the given name exists
=cut
sub check_plan_type {
my $self = shift;
my $name = shift;
my $dbh = Bugzilla->dbh;
my $type = $dbh->selectrow_hashref(
"SELECT 1
FROM test_plan_types
WHERE name = ?",
undef, $name);
return $type;
}
=head2 update_plan_type
Update the given type
=cut
sub update_plan_type {
my $self = shift;
my ($type_id, $name) = @_;
my $dbh = Bugzilla->dbh;
my $type = $dbh->do(
"UPDATE test_plan_types
SET name = ?
WHERE type_id = ?",
undef, ($name,$type_id));
}
=head2 add_plan_type
Add the given type
=cut
sub add_plan_type {
my $self = shift;
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my $type = $dbh->do(
"INSERT INTO test_plan_types (type_id, name)
VALUES(?,?)",
undef, (undef, $name));
}
=head2 get_fields
@@ -551,6 +648,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_plans");
$dbh->do("INSERT INTO test_plan_activity
(plan_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'plan_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field}));
@@ -609,6 +707,25 @@ sub lookup_type {
undef, $id);
return $value;
}
=head2 lookup_type_by_name
Returns the id of the type name passed.
=cut
sub lookup_type_by_name {
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($value) = $dbh->selectrow_array(
"SELECT type_id
FROM test_plan_types
WHERE name = ?",
undef, $name);
return $value;
}
=head2 lookup_status
Takes an ID of the status field and returns the value
@@ -665,9 +782,8 @@ Returns true if the logged in user has rights to view this plan
sub canview {
my $self = shift;
return $self->{'canview'} if exists $self->{'canview'};
$self->{'canview'} = Bugzilla::Testopia::Util::can_view_product($self->product_id);
return $self->{'canview'};
return 1 if (Bugzilla->user->id == $self->author->id);
return Bugzilla::Testopia::Util::can_view_product($self->product_id);
}
=head2 delete
@@ -703,10 +819,6 @@ Returns the product version for this object
Returns the product id for this object
=head2 editor
Returns a Bugzilla::User object representing the plan's last editor
=head2 author
Returns a Bugzilla::User object representing the plan author
@@ -729,7 +841,6 @@ sub id { return $_[0]->{'plan_id'}; }
sub creation_date { return $_[0]->{'creation_date'}; }
sub product_version { return $_[0]->{'default_product_version'}; }
sub product_id { return $_[0]->{'product_id'}; }
sub editor { return Bugzilla::User->new($_[0]->{'editor_id'}); }
sub author { return Bugzilla::User->new($_[0]->{'author_id'}); }
sub name { return $_[0]->{'name'}; }
sub type_id { return $_[0]->{'type_id'}; }

View File

@@ -161,7 +161,7 @@ sub calculate_percent_completed {
my $total = $idle + $run;
my $percent;
if ($total == 0) {
$percent = 100;
$percent = 0;
} else {
$percent = $run*100/$total;
$percent = int($percent + 0.5);
@@ -226,8 +226,8 @@ sub add_tag {
$dbh->bz_unlock_tables;
return 1;
}
$dbh->do("INSERT INTO test_run_tags VALUES(?,?)",
undef, $tag_id, $self->{'run_id'});
$dbh->do("INSERT INTO test_run_tags(tag_id, run_id, userid) VALUES(?,?,?)",
undef, $tag_id, $self->{'run_id'}, Bugzilla->user->id);
$dbh->bz_unlock_tables;
return 0;
@@ -259,6 +259,7 @@ the test_case_runs table
sub add_case_run {
my $self = shift;
my ($case_id) = @_;
return 0 if $self->check_case($case_id);
my $case = Bugzilla::Testopia::TestCase->new($case_id);
my $caserun = Bugzilla::Testopia::TestCaseRun->new({
'run_id' => $self->{'run_id'},
@@ -266,6 +267,7 @@ sub add_case_run {
'assignee' => $case->default_tester->id,
'case_text_version' => $case->version,
'build_id' => $self->build->id,
'environment_id' => $self->environment_id,
});
$caserun->store;
}
@@ -318,6 +320,7 @@ sub update {
# Update the history
my $field_id = Bugzilla::Testopia::Util::get_field_id($field, "test_runs");
$dbh->do("INSERT INTO test_run_activity
(run_id, fieldid, who, changed, oldvalue, newvalue)
VALUES(?,?,?,?,?,?)",
undef, ($self->{'run_id'}, $field_id, Bugzilla->user->id,
$timestamp, $self->{$field}, $newvalues->{$field}));
@@ -400,6 +403,25 @@ sub history {
return $ref;
}
=head2 Check_case
Checks if the given test case is already associated with this run
=cut
sub check_case {
my $self = shift;
my ($case_id) = @_;
my $dbh = Bugzilla->dbh;
my ($value) = $dbh->selectrow_array(
"SELECT case_run_id
FROM test_case_runs
WHERE case_id = ? AND run_id = ?",
undef, ($case_id, $self->{'run_id'}));
return $value;
}
=head2 lookup_environment
Takes an ID of the envionment field and returns the value
@@ -418,6 +440,23 @@ sub lookup_environment {
return $value;
}
=head2 lookup_environment_by_name
Takes the name of an envionment and returns its id
=cut
sub lookup_environment_by_name {
my ($name) = @_;
my $dbh = Bugzilla->dbh;
my ($value) = $dbh->selectrow_array(
"SELECT environment_id
FROM test_environments
WHERE name = ?",
undef, $name);
return $value;
}
=head2 lookup_build
Takes an ID of the build field and returns the value
@@ -454,6 +493,84 @@ sub lookup_manager {
return $value;
}
=head2 last_changed
Returns the date of the last change in the history table
=cut
sub last_changed {
my $self = shift;
my $dbh = Bugzilla->dbh;
my ($date) = $dbh->selectrow_array(
"SELECT MAX(changed)
FROM test_run_activity
WHERE run_id = ?",
undef, $self->id);
return $self->{'creation_date'} unless $date;
return $date;
}
sub filter_case_categories {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ids = $dbh->selectcol_arrayref(
"SELECT DISTINCT tcc.category_id
FROM test_case_categories AS tcc
JOIN test_cases ON test_cases.category_id = tcc.category_id
JOIN test_case_runs AS tcr ON test_cases.case_id = tcr.case_id
WHERE run_id = ?",
undef, $self->id);
my @categories;
foreach my $id (@$ids){
push @categories, Bugzilla::Testopia::Category->new($id);
}
return \@categories;
}
sub filter_builds {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ids = $dbh->selectcol_arrayref(
"SELECT DISTINCT build_id
FROM test_case_runs
WHERE run_id = ?",
undef, $self->id);
my @builds;
foreach my $id (@$ids){
push @builds, Bugzilla::Testopia::Build->new($id);
}
return \@builds;
}
sub filter_components {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $ids = $dbh->selectcol_arrayref(
"SELECT DISTINCT components.id
FROM components
JOIN test_case_components AS tcc ON tcc.component_id = components.id
JOIN test_cases ON test_cases.case_id = tcc.case_id
JOIN test_case_runs AS tcr ON test_cases.case_id = tcr.case_id
WHERE run_id = ?",
undef, $self->id);
my @components;
foreach my $id (@$ids){
push @components, Bugzilla::Component->new($id);
}
return \@components;
}
=head2 environments
Returns a reference to a list of Testopia::Environment objects.
@@ -960,6 +1077,39 @@ sub percent_complete {
return $self->{'percent_complete'};
}
sub percent_passed {
my $self = shift;
my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->failed_count + $self->blocked_count;
my $run = $self->passed_count;
$self->{'percent_passed'} = calculate_percent_completed($notrun, $run);
return $self->{'percent_passed'};
}
sub percent_failed {
my $self = shift;
my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->passed_count + $self->blocked_count;
my $run = $self->failed_count;
$self->{'percent_failed'} = calculate_percent_completed($notrun, $run);
return $self->{'percent_failed'};
}
sub percent_blocked {
my $self = shift;
my $notrun = $self->idle_count + $self->running_count + $self->paused_count + $self->passed_count + $self->failed_count;
my $run = $self->blocked_count;
$self->{'percent_blocked'} = calculate_percent_completed($notrun, $run);
return $self->{'percent_blocked'};
}
sub percent_not_run {
my $self = shift;
my $notrun = $self->failed_count + $self->running_count + $self->paused_count + $self->passed_count;
my $run = $self->idle_count + $self->blocked_count;
$self->{'percent_not_run'} = calculate_percent_completed($notrun, $run);
return $self->{'percent_not_run'};
}
=head2 current_caseruns
Returns a reference to a list of TestCaseRun objects that are the

View File

@@ -173,7 +173,8 @@ sub store {
undef, (undef, $self->{'tag_name'}));
$key = $dbh->bz_last_key( 'test_tags', 'tag_id' );
$dbh->bz_unlock_tables();
$self->{'tag_id'} = $key;
return $key;
}

View File

@@ -38,9 +38,9 @@ package Bugzilla::Testopia::Xml;
#use fields qw(testplans testcases tags categories builds);
use strict;
#use base qw(Exporter);
use Bugzilla::Config;
use Bugzilla::Error;
use Bugzilla::Product;
use Bugzilla::Testopia::Attachment;
use Bugzilla::Testopia::Build;
@@ -54,6 +54,27 @@ use Bugzilla::Testopia::XmlTestCase;
use Bugzilla::User;
use Bugzilla::Util;
use constant AUTOMATIC => "AUTOMATIC";
use constant BLOCKS => "blocks";
our $DATABASE_DESCRIPTION = "Database_descripiton";
our $DATABASE_ID = "Database_id";
use constant IGNORECASE => 1;
use constant DEPENDSON => "dependson";
use constant PCDATA => "#PCDATA";
our $TESTOPIA_GT = "&testopia_gt;";
our $TESTOPIA_LT = "&testopia_lt;";
use constant TESTPLAN_REFERENCE => "testplan_reference";
our $XML_DESCRIPTION = "Xml_description";
our $XML_AMP = "&[Aa][Mm][Pp];";
our $XML_APOS = "&[Aa][Pp][Oo][Ss];";
our $XML_GT = "&[Gg][Tt];";
our $XML_LT = "&[Ll][Tt];";
our $XML_QUOT = "&[Qq][Uu][Oo][Tt];";
use constant XMLREFERENCES_FIELDS => "Database_descripiton Database_id Xml_description";
@Bugzilla::Testopia::Xml::EXPORT = qw($DATABASE_DESCRIPTION $DATABASE_ID $XML_DESCRIPTION);
use Class::Struct;
#
# The Xml structure is used to keep track of all new Testopia objects being created.
@@ -62,13 +83,22 @@ struct
(
'Bugzilla::Testopia::Xml',
{
# Array of attachments read for xml source.
attachments => '@',
builds => '@',
#TODO builds => '@',
# Array of categories read from xml source. Categories
categories => '@',
tags => '@',
testenvironments => '@',
#TODO testenvironments => '@',
# Array of testcases read from xml source.
testcases => '@',
# Hash of testcase aliases indexed by testcase id. Used during verfication to
# insure that alias does not exist and that new aliases are used in only one testcase.
testcase_aliases => '%',
# Array of testplans read from xml source.
testplans => '@',
# If true indicates some type of error has occurred processing the XML. Used to prevent
# updating Testopia with contents of current XML.
parse_error => '$',
}
);
@@ -76,7 +106,6 @@ struct
#TODO: Add this to checksetup
use Text::Diff;
#use base qw(Exporter);
###############################
@@ -91,35 +120,11 @@ use Text::Diff;
#### Methods ####
###############################
=head2 new
Instantiate a new xml object. This takes a single argument
Instantiate a new test plan. This takes a single argument
either a test plan ID or a reference to a hash containing keys
identical to a test plan's fields and desired values.
=cut
#sub new {
# my Bugzilla::Testopia::Xml $self = fields::new(ref($invocant) || $invocant);
# $self->{testplans} = [];
# $self->{testcases} = [];
# $self->{tags} = [];
# $self->{categories} = [];
# $self->{builds} = [];
# return $self;
#}
sub debug_display()
{
my ($self) = @_;
my $attachments_array_ref = $self->attachments;
my $categories_array_ref = $self->categories;
my $testcases_array_ref = $self->testcases;
my $testplans_array_ref = $self->testplans;
foreach my $category ( @$categories_array_ref )
foreach my $category ( @{$self->categories})
{
bless($category,"Bugzilla::Testopia::Category");
@@ -131,7 +136,7 @@ sub debug_display()
print STDERR " Description " . $category->description() . "\n";
}
foreach my $testplan ( @$testplans_array_ref )
foreach my $testplan ( @{$self->testplans} )
{
bless($testplan,"Bugzilla::Testopia::TestPlan");
@@ -147,10 +152,6 @@ sub debug_display()
print STDERR " Description " . $testplan->text() . "\n";
print STDERR " Default Product Version " . $testplan->product_version() . "\n";
print STDERR " Document " . $testplan->text() . "\n";
my %editor = %{$testplan->editor()};
my $editor_id = $editor{"id"};
my $editor_login = $editor{"login"};
print STDERR " Editor " . $editor_login . " (id=" . $editor_id . ", hash=" . $testplan->editor() . ")\n";
print STDERR " Is Active " . $testplan->isactive() . "\n";
print STDERR " Product " . $testplan->product_id() . "\n";
print STDERR " Type " . $testplan->type_id() . "\n";
@@ -173,13 +174,68 @@ sub debug_display()
}
}
foreach my $testcase ( @$testcases_array_ref )
foreach my $testcase ( @{$self->testplans} )
{
bless($testcase,"Bugzilla::Testopia::XmlTestCase");
$testcase->debug_display();
}
}
=head2 entity_replace_testopia
Replace testopia entities in the string supplied.
This method is called for any field that is stored in HTML format. The Testopia entities are
provided to allow users to pass HTML tags.
For example, if you want to store a < in Bold font, the following XML
&testopia_lt;B&testopia_gt;&lt;&testopia_lt;/B&testopia_gt;
is passed to the HTML field as
<B>&lt;</B>
=cut
sub entity_replace_testopia
{
my ($string) = @_;
return undef if ( ! defined $string );
$string =~ s/$TESTOPIA_GT/>/g;
$string =~ s/$TESTOPIA_LT/</g;
$string =~ s/\n/<br>/g;
return $string;
}
=head2 entity_replace_xml
Replace xml entities in the string supplied.
This method is called for any field that is not stored in HTML format. The source is XML so any
XML entity will be in the &<name>; format. These entities need to be converted back to their
character representation.
=cut
sub entity_replace_xml
{
my ($string) = @_;
return undef if ( ! defined $string );
$string =~ s/$XML_GT/>/g;
$string =~ s/$XML_LT/</g;
$string =~ s/$XML_AMP/&/g;
$string =~ s/$XML_APOS/'/g;
$string =~ s/$XML_QUOT/"/g;
return $string;
}
sub error()
{
my ($self, $message) = @_;
@@ -192,15 +248,12 @@ sub error()
sub parse()
{
my ($self, $xml) = @_;
my $twig = XML::Twig->new();
my $twig = XML::Twig->new( load_DTD => 1, keep_encoding => 1 );
$twig->parse($xml);
my $root = $twig->root;
my $attachments_array_ref = $self->attachments;
my $categories_array_ref = $self->categories;
my $testplans_array_ref = $self->testplans;
my $testcases_array_ref = $self->testcases;
foreach my $twig_category ($root->children('category'))
{
my $product = new Bugzilla::Product({name => $twig_category->field('product')});
@@ -216,7 +269,7 @@ sub parse()
product_id => $product->id(),
description => $twig_category->field('description'),
});
push @$categories_array_ref, $category;
push @{$self->categories}, $category;
}
my $testplan = Bugzilla::Testopia::TestPlan->new({ 'name' => 'dummy' });
@@ -230,7 +283,7 @@ sub parse()
foreach my $twig_testplan ($root->children('testplan'))
{
my $author = $twig_testplan->field('author');
my $author = $twig_testplan->att('author');
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $author_ref = Bugzilla::User::match($author, 1, 0);
@@ -245,50 +298,43 @@ sub parse()
bless($author_user,"Bugzilla::User");
$author_id = $author_user->id();
}
my $editor = $twig_testplan->field('editor');
my $editor_ref = Bugzilla::User::match($editor, 1, 0);
my $editor_id = -1;
if ( ! $editor_ref->[0] )
my $product_id = Bugzilla::Testopia::TestPlan::lookup_product_by_name($twig_testplan->field('product'));
if ( ! defined($product_id) )
{
$self->error("Cannot find editor '" . $editor . "' in test plan '" . $twig_testplan->field('name') . "'.");
}
else
{
my $editor_user = $editor_ref->[0];
bless($editor_user,"Bugzilla::User");
$editor_id = $editor_user->id();
$self->error("Cannot find product '" . $twig_testplan->field('product') . "' in test plan '" . $twig_testplan->field('name') . "'.");
}
$testplan = Bugzilla::Testopia::TestPlan->new({
'name' => $twig_testplan->field('name'),
'product_id' => Bugzilla::Testopia::TestPlan::lookup_product_by_name($twig_testplan->field('product')),
'default_product_version' => $twig_testplan->field('productversion'),
'type_id' => $plantype_ids{$twig_testplan->field('type')},
'text' => $twig_testplan->field('document'),
'author_id' => $author_id,
'editor_id' => $editor_id,
'isactive' => $twig_testplan->field('archive'),
'creation_date' => $twig_testplan->field('created')
'name' => entity_replace_xml($twig_testplan->field('name')),
'product_id' => $product_id,
'default_product_version' => entity_replace_xml($twig_testplan->field('productversion')),
'type_id' => $plantype_ids{$twig_testplan->att('type')},
'text' => entity_replace_testopia($twig_testplan->field('document')),
'author_id' => $author_id,
'isactive' => entity_replace_xml($twig_testplan->field('archive')),
'creation_date' => entity_replace_xml($twig_testplan->field('created'))
});
push @$testplans_array_ref, $testplan;
push @{$self->testplans}, $testplan;
my @tags = $twig_testplan->children('tag');
foreach my $twig_tag (@tags)
{
push @{$self->tags}, $twig_tag->text();
push @{$self->tags}, entity_replace_xml($twig_tag->text());
}
my @attachments = $twig_testplan->children('attachment');
foreach my $twig_attachments (@attachments)
{
my $attachment = Bugzilla::Testopia::Attachment->new({
'description' => $twig_attachments->field('description'),
'filename' => $twig_attachments->field('filename'),
'submitter_id' => $twig_attachments->field('submitter'),
'mime_type' => $twig_attachments->field('mimetype'),
'creation_ts' => $twig_attachments->field('created'),
'data' => $twig_attachments->field('data')
'description' => entity_replace_xml($twig_attachments->field('description')),
'filename' => entity_replace_xml($twig_attachments->field('filename')),
'submitter_id' => entity_replace_xml($twig_attachments->field('submitter')),
'mime_type' => entity_replace_xml($twig_attachments->field('mimetype')),
'creation_ts' => entity_replace_xml($twig_attachments->field('created')),
'data' => entity_replace_xml($twig_attachments->field('data'))
});
push @$attachments_array_ref, $attachment;
push @{$self->attachments}, $attachment;
#$testplan->add_tag($tag->id());
}
}
@@ -309,14 +355,15 @@ sub parse()
}
foreach my $twig_testcase ($root->children('testcase'))
{
my $author = $twig_testcase->field('author');
my $summary = entity_replace_xml($twig_testcase->field('summary')) || undef;
my $author = $twig_testcase->att('author');
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $author_ref = Bugzilla::User::match($author, 1, 0);
my $author_id = -1;
if ( ! $author_ref->[0] )
{
$self->error("Cannot find author '" . $author . "' in test case '" . $twig_testcase->field('summary') . "'.");
$self->error("Cannot find author '" . $author . "' in test case '" . $summary . "'.");
}
else
{
@@ -324,14 +371,14 @@ sub parse()
bless($author_user,"Bugzilla::User");
$author_id = $author_user->id();
}
my $tester = $twig_testcase->field('defaulttester');
my $tester = entity_replace_xml($twig_testcase->field('defaulttester'));
# Bugzilla::User::match returns a array with a user hash. Fields of the hash needed
# are 'id' and 'login'.
my $tester_ref = Bugzilla::User::match($tester, 1, 0);
my $tester_id = -1;
if ( ! $tester_ref->[0] )
{
$self->error("Cannot find default tester '" . $tester . "' in test case '" . $twig_testcase->field('summary') . "'.");
$self->error("Cannot find default tester '" . $tester . "' in test case '" . $summary . "'.");
}
else
{
@@ -339,74 +386,158 @@ sub parse()
bless($tester_user,"Bugzilla::User");
$tester_id = $tester_user->id();
}
my $status_id = Bugzilla::Testopia::TestCase::lookup_status_by_name($twig_testcase->field('status'));
$self->error("Cannot find status '" . $twig_testcase->field('status') . "' in test case '" . $twig_testcase->field('summary') . "'.") if ( ! defined($status_id) );
my $status_id = Bugzilla::Testopia::TestCase::lookup_status_by_name($twig_testcase->att('status'));
$self->error("Cannot find status '" . $twig_testcase->att('status') . "' in test case '" . $summary . "'.") if ( ! defined($status_id) );
my $xml_testcase = new Bugzilla::Testopia::XmlTestCase;
$xml_testcase->blocks(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
$xml_testcase->dependson(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
$xml_testcase->testplan(Bugzilla::Testopia::XmlReferences->new(IGNORECASE, XMLREFERENCES_FIELDS));
push @{$self->testcases}, $xml_testcase;
my $alias = entity_replace_xml($twig_testcase->field('alias')) || undef;
$xml_testcase->testcase(Bugzilla::Testopia::TestCase->new({
'action' => $twig_testcase->field('action'),
'alias' => $twig_testcase->field('alias') || undef,
'arguments' => $twig_testcase->field('arguments'),
'action' => entity_replace_testopia($twig_testcase->field('action')),
'alias' => $alias,
'arguments' => entity_replace_xml($twig_testcase->field('arguments')),
'author_id' => $author_id,
#TODO: Blocks
'blocks' => undef,
'breakdown' => entity_replace_testopia($twig_testcase->field('breakdown')),
'case_status_id' => $status_id,
'category_id' => undef,
'creation_date' => $twig_testcase->field('created'),
'creation_date' => $twig_testcase->att('created'),
'default_tester_id' => $tester_id,
#TODO: Depends On
'dependson' => undef,
'effect' => $twig_testcase->field('effect'),
'expectedresults' => $twig_testcase->field('expectedresults'),
'isautomated' => $twig_testcase->field('isautomated'),
'effect' => entity_replace_testopia($twig_testcase->field('expectedresults')),
'isautomated' => ( uc $twig_testcase->att('automated') ) eq AUTOMATIC,
'plans' => undef,
'priority_id' => $priority_ids{$twig_testcase->field('priority')},
'requirement' => $twig_testcase->field('requirement'),
'script' => $twig_testcase->field('script'),
'summary' => $twig_testcase->field('summary'),
'priority_id' => $priority_ids{$twig_testcase->att('priority')},
'requirement' => entity_replace_xml($twig_testcase->field('requirement')),
'setup' => entity_replace_testopia($twig_testcase->field('setup')),
'script' => entity_replace_xml($twig_testcase->field('script')),
'summary' => $summary,
}));
# Action field is html format.
my $action = $twig_testcase->field('action');
$action =~ s/\n/<br>/g;
$xml_testcase->action($action);
$xml_testcase->category($twig_testcase->field('category'));
# Expectedresults field is html format.
my $expectedresults = $twig_testcase->field('expectedresults');
$expectedresults =~ s/\n/<br>/g;
$xml_testcase->expectedresults($expectedresults);
push @$testcases_array_ref, $xml_testcase;
foreach my $twig_testplan_reference ( $twig_testcase->children(TESTPLAN_REFERENCE) )
{
my $testplan_reference = $twig_testplan_reference->children_text(PCDATA);
if ( $testplan_reference eq "" )
{
$self->error("No test plan included for type '"
. $twig_testplan_reference->att('type')
. "' in test case '" . $twig_testcase->field('summary') . "'." );
}
elsif ( ! $xml_testcase->testplan->add($twig_testplan_reference->att('type'),entity_replace_xml($testplan_reference)) )
{
$self->error("Do not know how to handle test plan of type '"
. $twig_testplan_reference->att('type')
. "' in test case '" . $twig_testcase->field('summary') . "'."
. "\nKnow types are: (" . uc XMLREFERENCES_FIELDS . ").");
}
}
# Keep track of this testcase's alias. Used during verification to insure aliases are unique.
$self->testcase_aliases(entity_replace_xml($twig_testcase->field('summary')),$alias) if ( defined $alias );
# Keep track of this testcase's category. To create a category at this time would require
# getting the product from the Test Plan that this Test Case is associated with. The category
# will created when each Test Case is stored.
$xml_testcase->category(entity_replace_xml($twig_testcase->field('categoryname')));
my @tags = $twig_testcase->children('tag');
foreach my $twig_tag (@tags)
foreach my $twig_tag ( @tags )
{
$xml_testcase->add_tag($twig_tag->text());
$xml_testcase->add_tag(entity_replace_xml($twig_tag->text()));
}
#my @components;
#foreach my $id (@comps){
# detaint_natural($id);
# validate_selection($id, 'id', 'components');
# push @components, $id;
#}
#$case->add_component($_) foreach (@components);
my @components = $twig_testcase->children('component');
foreach my $twig_component ( @components )
{
$xml_testcase->add_component(entity_replace_xml($twig_component->text()));
}
foreach my $twig_blocks ( $twig_testcase->children(BLOCKS) )
{
if ( ! $xml_testcase->blocks->add($twig_blocks->att('type'),entity_replace_xml($twig_blocks->children_text(PCDATA))) )
{
$self->error("Do not know how to handle a blocking test case of type '" . $twig_blocks->att('type') . "' in test case '" . $xml_testcase->testcase->summary() . "'.")
}
}
foreach my $twig_dependson ( $twig_testcase->children(DEPENDSON) )
{
if ( ! $xml_testcase->dependson->add($twig_dependson->att('type'),entity_replace_xml($twig_dependson->children_text(PCDATA))) )
{
$self->error("Do not know how to handle dependency of type '" . $twig_dependson->att('type') . "' in test case '" . entity_replace_xml($xml_testcase->testcase->summary()) . "'.")
}
}
}
#
# Start of data integrity check.
#
# Run through the Test Plans and Test Cases looking for integrity errors.
#
# Check for duplicate aliases. Loop though all testcases that have a alias.
my %used_alias;
my %duplicate_alias;
foreach my $summary ( keys %{$self->testcase_aliases} )
{
# Get the alias.
my $alias = $self->testcase_aliases($summary);
# Is the alias used by a testcase in the database already? If so add it to the duplicate list
# and move onto next testcase.
my $alias_testcase_id = Bugzilla::Testopia::TestCase::class_check_alias($alias);
if ( $alias_testcase_id )
{
$duplicate_alias{$alias} = $alias_testcase_id;
next;
}
# Is the alias in the used_alias array?
if ( defined( $used_alias{$alias} ) )
{
# If so then another testcase being created also used the alias. Add the alias to the
# duplicate list.
$duplicate_alias{$alias} = "import";
}
else
{
# Alias has not been seen yet. Add it to the used_alias list to keep track of it.
$used_alias{$alias} = "";
}
}
# The @duplicate_alias list contains aliases used by more that one test case. Display them and set
# error condition
foreach my $summary ( keys %{$self->testcase_aliases} )
{
my $alias = $self->testcase_aliases($summary);
if ( exists $duplicate_alias{$alias} )
{
my $error_message = "Test Case '" . $summary . "' has a non-unique alias '" . $alias . "'.";
if ( $duplicate_alias{$alias} ne "import" )
{
$error_message .= " Test Case " . $duplicate_alias{$alias} . " already uses the alias '" . $alias . "'.";
}
else
{
$error_message .= " Additional test cases being imported are using the alias '" . $alias . "'.";
}
$self->error($error_message);
}
}
#TODO Verfication of data.
#Test cases require a category or store will fail.
# Don't really care about the value of parse_error. If it has been defined then a error occurred
# parsing the XML.
#
# Start of data store.
#
# No data has been written prior to this point. If parse_error has not been set the XML is valid
# and no integrity errors were found. It's time to start storing the Test Plans and Test Cases.
#
if ( ! defined $self->parse_error )
{
# $self->debug_display();
my @testplanarray;
foreach my $testplan ( @{$self->testplans} )
{
my $plan_id = $testplan->store();
$testplan->{'plan_id'} = $plan_id;
print "Created Test Plan $plan_id: " . $testplan->name() . "\n";
foreach my $asciitag ( @{$self->tags} )
{
my $classtag = Bugzilla::Testopia::TestTag->new({'tag_name' => $asciitag});
@@ -414,20 +545,30 @@ sub parse()
$testplan->{'tag_id'} = $tagid;
$testplan->add_tag($tagid);
}
push @testplanarray, $testplan;
}
# Store the testcases.
foreach my $testcase ( @{$self->testcases} )
{
bless($testcase,"Bugzilla::Testopia::XmlTestCase");
my $result = $testcase->store($testplan->{'author_id'},@testplanarray);
my $result = $testcase->store(@{$self->testplans});
if ( $result ne "" )
{
$self->error($result);
}
else
{
print "Created Test Case " . $testcase->testcase->id() . ": " . $testcase->testcase->summary() . "\n";
}
}
# Now that each testcase has been stored we loop though them again and create
# relationships like blocks or dependson.
foreach my $testcase ( @{$self->testcases} )
{
$testcase->store_relationships(@{$self->testcases});
}
}
$twig->purge;
}

View File

@@ -0,0 +1,101 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Test Runner System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): David Koenig <dkoenig@novell.com>
=head1 NAME
Bugzilla::Testopia::XmlReferences - Testopia XmlReferences object
=head1 DESCRIPTION
This module maintains references to objects while the XML data is
being imported. Test plans and Test cases can be referenced by
database id, database description or XML description. The references
are stored here and processed as needed.
=head1 SYNOPSIS
use Bugzilla::Testopia::XmlReferences;
=cut
package Bugzilla::Testopia::XmlReferences;
use constant IGNORECASE => "ignorecase";
#use strict;
sub new
{
my ($invocant,$ignorecase,$fields) = @_;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
$self{IGNORECASE} = $ignorecase;
for my $field ( split(/ /, $fields) )
{
$field = uc $field if ( $self{IGNORECASE} );
$self->{$field} = [];
}
return $self;
}
sub add
{
my ($self, $type, $object) = @_;
$type = uc $type if ( $self{IGNORECASE} );
return 0 if ( ! exists $self->{$type} );
push @{$self->{$type}}, $object;
}
sub display
{
my ($self) = @_;
print "display() self=" . $self . "\n";
foreach $key (keys %$self)
{
if ( defined $self->{$key} )
{
print "display() key=$key value=" . $self->{$key} . "\n";
}
else
{
print "display() key=$key value=undefined\n";
}
}
}
sub get
{
my ($self, $type) = @_;
$type = uc $type if ( $self{IGNORECASE} );
return 0 if ( ! exists $self->{$type} );
return $self->{$type};
}
1;

View File

@@ -48,6 +48,7 @@ use Bugzilla::Testopia::TestPlan;
use Bugzilla::Testopia::TestRun;
use Bugzilla::Testopia::TestTag;
use Bugzilla::Testopia::Util;
use Bugzilla::Testopia::XmlReferences;
use Bugzilla::User;
use Bugzilla::Util;
@@ -62,129 +63,224 @@ struct
(
'Bugzilla::Testopia::XmlTestCase',
{
action => '$',
blocks => '@',
category => '$',
dependson => '@',
expectedresults => '$',
tags => '@',
testcase => 'Bugzilla::Testopia::TestCase',
# action => '$',
blocks => 'Bugzilla::Testopia::XmlReferences',
category => '$',
components => '@',
dependson => 'Bugzilla::Testopia::XmlReferences',
# expectedresults => '$',
tags => '@',
testcase => 'Bugzilla::Testopia::TestCase',
testplan => 'Bugzilla::Testopia::XmlReferences',
}
);
sub add_tag()
{
my ($self,$tag) = @_;
push @{$self->tags}, $tag;
}
sub add_component()
{
my ($self,$component) = @_;
push @{$self->components}, $component;
}
sub debug_display()
{
my ($self) = @_;
my $display_variable = "";
my %text = %{$self->testcase->text()} if ( defined $self->testcase->text() );
print STDERR "Testcase: " . $self->testcase->summary() . "\n";
my $testcase_id = "null";
$testcase_id = $self->testcase->id() if ( $self->testcase->id() );
print STDERR " ID " . $testcase_id . "\n";
print STDERR " Action\n";
my @results = split(/\n/,$self->action);
my $testcase_id = "null";
$testcase_id = $self->testcase->id() if ( $self->testcase->id() );
print STDERR " ID " . $testcase_id . "\n";
print STDERR " Action\n";
if ( defined $text{'action'} )
{
my @results = split(/\n/,$text{'action'});
foreach my $result (@results)
{
print STDERR " $result\n";
}
my $alias = "null";
$alias = $self->testcase-alias() if ( $self->testcase->alias() );
print STDERR " Alias " . $alias . "\n";
my %author = %{$self->testcase->author()};
my $author_id = $author{"id"};
my $author_login = $author{"login"};
print STDERR " Author " . $author_login . " (id=" . $author_id . ", hash=" . $self->testcase->author() . ")\n";
foreach my $loop ( @{$self->blocks} )
{
$display_variable .= $loop . ",";
}
chop $display_variable;
print STDERR " Blocks " . $display_variable . "\n";
$display_variable = "";
print STDERR " Category " . $self->category . "\n";
print STDERR " Creation Date " . $self->testcase->creation_date() . "\n";
foreach my $loop ( @{$self->blocks} )
{
$display_variable .= $loop . ",";
}
chop $display_variable;
print STDERR " Dependson " . $display_variable . "\n";
$display_variable = "";
print STDERR " Expected Results\n";
@results = split(/\n/,$self->expectedresults);
}
my $alias = "null";
$alias = $self->testcase-alias() if ( $self->testcase->alias() );
print STDERR " Alias " . $alias . "\n";
my %author = %{$self->testcase->author()};
my $author_id = $author{"id"};
my $author_login = $author{"login"};
print STDERR " Author " . $author_login . " (id=" . $author_id . ", hash=" . $self->testcase->author() . ")\n";
print STDERR " Blocks " . $self->blocks->display() . "\n";
print STDERR " Category " . $self->category . "\n";
$display_variable = $self->testcase->creation_date();
if ( defined $display_variable )
{
print STDERR " Creation Date " . $display_variable . "\n";
}
print STDERR " Depends On " . $self->dependson->display() . "\n";
print STDERR " Expected Results\n";
if ( defined $text{'effect'} )
{
my @results = split(/\n/,$text{'effect'});
foreach my $result (@results)
{
print STDERR " $result\n";
}
print STDERR " Summary " . $self->testcase->summary() . "\n";
#TODO:print STDERR " Default Product Version " . $self->testcase->product_version() . "\n";
#TODO:print STDERR " Document " . $self->testcase->text() . "\n";
my %tester = %{$self->testcase->default_tester()};
my $tester_id = $tester{"id"};
my $tester_login = $tester{"login"};
print STDERR " Tester " . $tester_login . " (id=" . $tester_id . ", hash=" . $self->testcase->default_tester() . ")\n";
print STDERR " Is Automated " . $self->testcase->isautomated() . "\n";
#TODO:print STDERR " Plans " . $self->testcase->plans() . "\n";
#TODO:print STDERR " Priority " . $self->testcase->priority_id() . "\n";
#TODO:print STDERR " Product " . $self->testcase->product_id() . "\n";
print STDERR " Requirement " . $self->testcase->requirement() . "\n";
}
print STDERR " Summary " . $self->testcase->summary() . "\n";
#TODO:print STDERR " Default Product Version " . $self->testcase->product_version() . "\n";
#TODO:print STDERR " Document " . $self->testcase->text() . "\n";
my %tester = %{$self->testcase->default_tester()};
my $tester_id = $tester{"id"};
my $tester_login = $tester{"login"};
print STDERR " Tester " . $tester_login . " (id=" . $tester_id . ", hash=" . $self->testcase->default_tester() . ")\n";
print STDERR " Is Automated " . $self->testcase->isautomated() . "\n";
#TODO:print STDERR " Plans " . $self->testcase->plans() . "\n";
#TODO:print STDERR " Priority " . $self->testcase->priority_id() . "\n";
#TODO:print STDERR " Product " . $self->testcase->product_id() . "\n";
print STDERR " Requirement " . $self->testcase->requirement() . "\n";
print STDERR " Script " . $self->testcase->script() . "\n";
print STDERR " Script Arguments " . $self->testcase->arguments() . "\n";
print STDERR " Status " . $self->testcase->status() . "\n";
print STDERR " Script " . $self->testcase->script() . "\n";
print STDERR " Script Arguments " . $self->testcase->arguments() . "\n";
print STDERR " Status " . $self->testcase->status() . "\n";
foreach my $tag (@{$self->tags})
{
print STDERR " Tag " . $tag . "\n";
}
foreach my $tag (@{$self->tags})
{
print STDERR " Tag " . $tag . "\n";
}
my @attachments = @{$self->testcase->attachments()};
foreach my $attachment (@attachments)
{
my %submitter = %{$self->testcase->submitter()};
my $author_login = $author{"login"};
print STDERR " Attachment " . $attachment->description() . "\n";
print STDERR " Creation Date " . $attachment->creation_ts(). "\n";
print STDERR " Filename " . $attachment->filename() . "\n";
print STDERR " Mime Type " . $attachment->mime_type(). "\n";
print STDERR " Submitter " . $author_login . "\n";
}
my @attachments = @{$self->testcase->attachments()};
foreach my $attachment (@attachments)
{
my %submitter = %{$self->testcase->submitter()};
$author_login = $author{"login"};
print STDERR " Attachment " . $attachment->description() . "\n";
print STDERR " Creation Date " . $attachment->creation_ts(). "\n";
print STDERR " Filename " . $attachment->filename() . "\n";
print STDERR " Mime Type " . $attachment->mime_type(). "\n";
print STDERR " Submitter " . $author_login . "\n";
}
}
sub add_tag()
sub get_testcase_ids()
{
my ($self,$tag) = @_;
push @{$self->tags}, $tag;
my ($self, $field, @new_testcases) = @_;
my $error_message = "";
my @testcase_id = @{$self->$field->get(uc $Bugzilla::Testopia::Xml::DATABASE_ID)};
foreach my $testcase_summary ( @{$self->$field->get(uc $Bugzilla::Testopia::Xml::XML_DESCRIPTION)} )
{
foreach my $testcase (@new_testcases)
{
push @testcase_id, $testcase->testcase->id() if ( $testcase->testcase->summary() eq $testcase_summary );
}
}
#TODO Testplans using Database_Description
foreach my $testcase_summary ( @{$self->$field->get(uc $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION)} )
{
$error_message .= "\n" if ( $error_message ne "" );
$error_message .= "Have not implemented code for $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION lookup for blocking test case " . $testcase_summary . "' for Test Case '". $self->testcase->summary . "'.";
}
return $error_message if ( $error_message ne "" );
my @return_testcase_id;
foreach my $testcase_id (@testcase_id)
{
my $testcase = Bugzilla::Testopia::TestCase->new($testcase_id);
if ( ! defined($testcase) )
{
$error_message .= "\n" if ( $error_message ne "" );
$error_message .= "Could not find blocking Test Case '" . $testcase_id . "' for Test Case '". $self->testcase->summary . "'.";
}
else
{
push @return_testcase_id, $testcase->id();
}
}
return $error_message if ( $error_message ne "" );
return @return_testcase_id;
}
sub store()
{
my ($self, $userid, @testplans) = @_;
my ($self, @new_testplans) = @_;
my $error_message = "";
my @testplan_id = @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::DATABASE_ID)};
#
# Following code pulled from Bugzilla::Testopia::TestCase->get_category_list().
# Cannot call Bugzilla::Testopia::TestCase->get_category_list() until we create a
# TestCase but cannot create a TestCase without a category.
#
my @categories;
foreach my $testplan (@testplans)
# If we have any references to test plans from the XML data we need to search the @new_testplans
# array to find the actual test plan id. The new_testplans array contains all test plans created
# from the XML.
# Order of looping does not matter, number of test plans associated to each test case should be small.
foreach my $testplan_name ( @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::XML_DESCRIPTION)} )
{
push @categories, @{$testplan->categories};
}
my $categoryid = -1;
foreach my $category (@categories)
{
my %temphash = %{$category};
if ( $self->category eq $temphash{"name"} )
foreach my $testplan (@new_testplans)
{
$categoryid = $temphash{"category_id"};
last;
push @testplan_id, $testplan->id() if ( $testplan->name() eq $testplan_name );
}
}
return "Cannot save test case '" . $self->testcase->summary . "', Category '" . $self->category . "' is not valid." if ( $categoryid == -1 );
$self->testcase->{'category_id'} = $categoryid;
}
#TODO Testplans using Database_Description
foreach my $testplan_name ( @{$self->testplan->get(uc $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION)} )
{
$error_message .= "\n" if ( $error_message ne "" );
$error_message .= "Have not implemented code for $Bugzilla::Testopia::Xml::DATABASE_DESCRIPTION lookup of test plan " . $testplan_name . "' for Test Case '". $self->testcase->summary . "'.";
}
return $error_message if ( $error_message ne "" );
# Have to have a testplan to determine valid categories for testcase.
return "Test Case '" . $self->testcase->summary . "' needs a Test Plan." if ( $#testplan_id == -1 );
# Verify that each testplan exists.
my @testplan;
foreach my $testplan_id (@testplan_id)
{
my $testplan = Bugzilla::Testopia::TestPlan->new($testplan_id);
if ( ! defined($testplan) )
{
$error_message .= "\n" if ( $error_message ne "" );
$error_message .= "Could not find Test Plan '" . $testplan_id . "' for Test Case '". $self->testcase->summary . "'.";
}
else
{
push @testplan, $testplan;
}
}
return $error_message if ( $error_message ne "" );
# Verify that each testplan has the testcase's category associated to it. If the category does not
# exist it will be created.
foreach my $testplan (@testplan)
{
my $categoryid = -1;
push my @categories, @{$testplan->categories};
foreach my $category (@categories)
{
if ( $category->name eq $self->category )
{
$categoryid = $category->id;
last;
}
}
if ( $categoryid == -1 )
{
my $new_category = Bugzilla::Testopia::Category->new({
product_id => $testplan->product_id,
name => $self->category,
description => "FIX ME. Created during Test Plan import."
});
$categoryid = $new_category->store();
}
$self->testcase->{'category_id'} = $categoryid if ( ! defined($self->testcase->{'category_id'}) );
}
my $case_id = $self->testcase->store();
$self->testcase->{'case_id'} = $case_id;
foreach my $asciitag ( @{$self->tags} )
@@ -193,13 +289,33 @@ sub store()
my $tagid = $classtag->store;
$self->testcase->add_tag($tagid);
}
foreach my $testplan ( @testplans )
# Link the testcase to each of it's testplans.
foreach my $testplan ( @testplan )
{
$self->testcase->link_plan($testplan->id(),$case_id)
}
$self->testcase->store_text($case_id,$userid,$self->action,$self->expectedresults);
return "";
# Code below requires the testplans to be linked into testcases before being run.
foreach my $component ( @{$self->components} )
{
foreach my $available_component ( @{$self->testcase->get_selectable_components()} )
{
$self->testcase->add_component($available_component->{'id'}) if ( $component eq $available_component->{'name'} );
}
}
return $error_message;
}
sub store_relationships()
{
my ($self, @new_testcases) = @_;
my $blocks = $self->testcase->blocked_list_uncached() . " " . join(' ',$self->get_testcase_ids("blocks",@new_testcases));
my $dependson = $self->testcase->dependson_list_uncached() . " " . join(' ',$self->get_testcase_ids("dependson",@new_testcases));
$self->testcase->update_deps($dependson,$blocks);
}
1;

View File

@@ -0,0 +1,36 @@
# -*- 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 Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
package Bugzilla::WebService::Constants;
use strict;
use base qw(Exporter);
@Bugzilla::WebService::Constants::EXPORT = qw(
ERROR_AUTH_NODATA
ERROR_UNIMPLEMENTED
ERROR_GENERAL
ERROR_FAULT_SERVER
);
use constant ERROR_AUTH_NODATA => 410;
use constant ERROR_UNIMPLEMENTED => 910;
use constant ERROR_GENERAL => 999;
# RPC Fault Code must be an integer
use constant ERROR_FAULT_SERVER => 998;
1;

View File

@@ -0,0 +1,257 @@
# -*- 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 Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Dallas Harken <dharken@novell.com>
package Bugzilla::WebService::Testopia::TestCase;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::User;
use Bugzilla::Util qw(detaint_natural);
use Bugzilla::Testopia::TestCase;
use Bugzilla::Testopia::Search;
use Bugzilla::Testopia::Table;
# Convert string field values to their respective integer id's
sub _convert_to_ids
{
my ($hash) = @_;
if (defined($$hash{"author"}))
{
$$hash{"author_id"} = login_to_id($$hash{"author"});
}
delete $$hash{"author"};
if (defined($$hash{"case_status"}))
{
$$hash{"case_status_id"} = Bugzilla::Testopia::TestCase::lookup_status_by_name($$hash{"case_status"});
}
delete $$hash{"case_status"};
if (defined($$hash{"category"}))
{
$$hash{"category_id"} = Bugzilla::Testopia::TestCase::lookup_category_by_name($$hash{"category"});
}
delete $$hash{"category"};
if (defined($$hash{"default_tester"}))
{
$$hash{"default_tester_id"} = login_to_id($$hash{"default_tester"});
}
delete $$hash{"default_tester"};
if (defined($$hash{"priority"}))
{
$$hash{"priority_id"} = Bugzilla::Testopia::TestCase::lookup_priority_by_value($$hash{"priority"});
}
delete $$hash{"priority"};
}
# Convert fields with integer id's to their respective string values
sub _convert_to_strings
{
my ($hash) = @_;
$$hash{"author"} = "";
if (defined($$hash{"author_id"}))
{
$$hash{"author"} = new Bugzilla::User($$hash{"author_id"})->login;
}
delete $$hash{"author_id"};
$$hash{"case_status"} = "";
if (defined($$hash{"case_status_id"}))
{
$$hash{"case_status"} = Bugzilla::Testopia::TestCase->lookup_status($$hash{"case_status_id"});
}
delete $$hash{"case_status_id"};
$$hash{"category"} = "";
if (defined($$hash{"category_id"}))
{
$$hash{"category"} = Bugzilla::Testopia::TestCase->lookup_category($$hash{"category_id"});
}
delete $$hash{"category_id"};
$$hash{"default_tester"} = "";
if (defined($$hash{"default_tester_id"}))
{
$$hash{"default_tester"} = new Bugzilla::User($$hash{"default_tester_id"})->login;
}
delete $$hash{"default_tester_id"};
$$hash{"priority"} = "";
if (defined($$hash{"priority_id"}))
{
$$hash{"priority"} = Bugzilla::Testopia::TestCase->lookup_priority($$hash{"priority_id"});
}
delete $$hash{"priority_id"};
}
# Utility method called by the list method
sub _list
{
my ($query) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param("viewall", 1);
$cgi->param("current_tab", "case");
foreach (keys(%$query))
{
$cgi->param($_, $$query{$_});
}
my $search = Bugzilla::Testopia::Search->new($cgi);
# Result is an array of test plan hash maps
return Bugzilla::Testopia::Table->new('case',
'tr_xmlrpc.cgi',
$cgi,
undef,
$search->query()
)->list();
}
sub get
{
my $self = shift;
my ($test_case_id) = @_;
Bugzilla->login;
# We can detaint immediately if what we get passed is fully numeric.
# We leave bug alias checks to Bugzilla::Testopia::TestCase::new.
if ($test_case_id =~ /^[0-9]+$/) {
detaint_natural($test_case_id);
}
#Result is a test case hash map
my $testcase = new Bugzilla::Testopia::TestCase($test_case_id);
_convert_to_strings($testcase);
Bugzilla->logout;
return $testcase;
}
sub list
{
Bugzilla->login;
my $self = shift;
my ($query) = @_;
_convert_to_ids($query);
my $list = _list($query);
foreach (@$list)
{
_convert_to_strings($_);
}
Bugzilla->logout;
return $list;
}
sub create
{
my $self =shift;
my ($new_values) = @_;
Bugzilla->login;
_convert_to_ids($new_values);
my $test_case = new Bugzilla::Testopia::TestCase($new_values);
Bugzilla->logout;
# Result is new test plan id
return $test_case->store();
}
sub update
{
my $self =shift;
my ($test_case_id, $new_values) = @_;
Bugzilla->login;
my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
_convert_to_ids($new_values);
$test_case->update($new_values);
Bugzilla->logout;
# Result is zero on success, otherwise an exception will be thrown
return 0;
}
sub get_text
{
my $self =shift;
my ($test_case_id) = @_;
Bugzilla->login;
my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
my $doc = $test_case->text();
$$doc{"author"} = "";
if (defined($$doc{"author_id"}))
{
$$doc{"author"} = new Bugzilla::User($$doc{"author_id"})->login;
}
delete $$doc{"author_id"};
Bugzilla->logout;
#Result is the latest test case doc hash map
return $doc;
}
sub store_text
{
my $self =shift;
my ($test_case_id, $author, $action, $effect) = @_;
Bugzilla->login;
my $author_id = login_to_id($author);
my $test_case = new Bugzilla::Testopia::TestCase($test_case_id);
my $version = $test_case->store_text($test_case_id, $author_id, $action, $effect);
Bugzilla->logout;
# Result is new test case doc version on success, otherwise an exception will be thrown
return $version;
}
1;

View File

@@ -0,0 +1,186 @@
# -*- 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 Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Dallas Harken <dharken@novell.com>
package Bugzilla::WebService::Testopia::TestCaseRun;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Util qw(detaint_natural);
use Bugzilla::Testopia::TestCaseRun;
use Bugzilla::User;
use Bugzilla::Testopia::Search;
use Bugzilla::Testopia::Table;
# Convert string field values to their respective integer id's
sub _convert_to_ids
{
my ($hash) = @_;
if (defined($$hash{"assignee"}))
{
$$hash{"assignee"} = login_to_id($$hash{"assignee"});
}
if (defined($$hash{"testedby"}))
{
$$hash{"testedby"} = login_to_id($$hash{"testedby"});
}
if (defined($$hash{"case_run_status"}))
{
$$hash{"case_run_status_id"} = Bugzilla::Testopia::TestCaseRun::lookup_status_by_name($$hash{"case_run_status"});
}
delete $$hash{"case_run_status"};
}
# Convert fields with integer id's to their respective string values
sub _convert_to_strings
{
my ($hash) = @_;
if (defined($$hash{"assignee"}))
{
$$hash{"assignee"} = new Bugzilla::User($$hash{"assignee"})->login;
}
if (defined($$hash{"testedby"}))
{
$$hash{"testedby"} = new Bugzilla::User($$hash{"testedby"})->login;
}
$$hash{"case_run_status"} = "";
if (defined($$hash{"case_run_status_id"}))
{
$$hash{"case_run_status"} = Bugzilla::Testopia::TestCaseRun->lookup_status($$hash{"case_run_status_id"});
}
delete $$hash{"case_run_status_id"};
}
# Utility method called by the list method
sub _list
{
my ($query) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param("viewall", 1);
$cgi->param("current_tab", "case_run");
foreach (keys(%$query))
{
$cgi->param($_, $$query{$_});
}
my $search = Bugzilla::Testopia::Search->new($cgi);
# Result is an array of test case run hash maps
return Bugzilla::Testopia::Table->new('case_run',
'tr_xmlrpc.cgi',
$cgi,
undef,
$search->query()
)->list();
}
sub get
{
my $self = shift;
my ($test_case_run_id) = @_;
Bugzilla->login;
# We can detaint immediately if what we get passed is fully numeric.
# We leave bug alias checks to Bugzilla::Testopia::TestCaseRun::new.
if ($test_case_run_id =~ /^[0-9]+$/) {
detaint_natural($test_case_run_id);
}
#Result is a test case run hash map
my $test_case_run = new Bugzilla::Testopia::TestCaseRun($test_case_run_id);
_convert_to_strings($test_case_run);
Bugzilla->logout;
return $test_case_run;
}
sub list
{
Bugzilla->login;
my $self = shift;
my ($query) = @_;
_convert_to_ids($query);
my $list = _list($query);
foreach (@$list)
{
_convert_to_strings($_);
}
Bugzilla->logout;
return $list;
}
sub create
{
my $self =shift;
my ($new_values) = @_;
Bugzilla->login;
_convert_to_ids($new_values);
my $test_case_run = new Bugzilla::Testopia::TestCaseRun($new_values);
Bugzilla->logout;
# Result is new test case run id
return $test_case_run->store();
}
sub update
{
my $self =shift;
my ($run_id, $case_id, $build_id, $new_values) = @_;
Bugzilla->login;
my $test_case_run_id = Bugzilla::Testopia::TestCaseRun::lookup_case_run_id($run_id, $case_id, $build_id);
my $test_case_run = new Bugzilla::Testopia::TestCaseRun($test_case_run_id);
_convert_to_ids($new_values);
$$new_values{build_id} = $build_id; # Needed by TestCaseRun's update member function
$test_case_run->update($new_values);
Bugzilla->logout;
# Result is zero on success, otherwise an exception will be thrown
return 0;
}
1;

View File

@@ -0,0 +1,190 @@
# -*- 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 Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Dallas Harken <dharken@novell.com>
package Bugzilla::WebService::Testopia::TestPlan;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Util qw(detaint_natural);
use Bugzilla::Product;
use Bugzilla::User;
use Bugzilla::Testopia::TestPlan;
use Bugzilla::Testopia::Search;
use Bugzilla::Testopia::Table;
# Convert string field values to their respective integer id's
sub _convert_to_ids
{
my ($hash) = @_;
if (defined($$hash{"author"}))
{
$$hash{"author_id"} = login_to_id($$hash{"author"});
}
delete $$hash{"author"};
if (defined($$hash{"product"}))
{
my $product = Bugzilla::Product::check_product($$hash{"product"});
$$hash{"product_id"} = $product->id;
}
delete $$hash{"product"};
if (defined($$hash{"type"}))
{
$$hash{"type_id"} = Bugzilla::Testopia::TestPlan::lookup_type_by_name($$hash{"type"});
}
delete $$hash{"type"};
}
# Convert fields with integer id's to their respective string values
sub _convert_to_strings
{
my ($hash) = @_;
$$hash{"author"} = "";
if (defined($$hash{"author_id"}))
{
$$hash{"author"} = new Bugzilla::User($$hash{"author_id"})->login;
}
delete $$hash{"author_id"};
$$hash{"product"} = "";
if (defined($$hash{"product_id"}))
{
$$hash{"product"} = new Bugzilla::Product($$hash{"product_id"})->name;
}
delete $$hash{"product_id"};
$$hash{"type"} = "";
if (defined($$hash{"type_id"}))
{
$$hash{"type"} = Bugzilla::Testopia::TestPlan->lookup_type($$hash{"type_id"});
}
delete $$hash{"type_id"};
}
# Utility method called by the list method
sub _list
{
my ($query) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param("viewall", 1);
$cgi->param("current_tab", "plan");
foreach (keys(%$query))
{
$cgi->param($_, $$query{$_});
}
my $search = Bugzilla::Testopia::Search->new($cgi);
# Result is an array of test plan hash maps
return Bugzilla::Testopia::Table->new('plan',
'tr_xmlrpc.cgi',
$cgi,
undef,
$search->query()
)->list();
}
sub get
{
my $self = shift;
my ($test_plan_id) = @_;
Bugzilla->login;
# We can detaint immediately if what we get passed is fully numeric.
# We leave bug alias checks to Bugzilla::Testopia::TestPlan::new.
if ($test_plan_id =~ /^[0-9]+$/) {
detaint_natural($test_plan_id);
}
#Result is a test plan hash map
my $testplan = new Bugzilla::Testopia::TestPlan($test_plan_id);
_convert_to_strings($testplan);
Bugzilla->logout;
return $testplan;
}
sub list
{
Bugzilla->login;
my $self = shift;
my ($query) = @_;
_convert_to_ids($query);
my $list = _list($query);
foreach (@$list)
{
_convert_to_strings($_);
}
Bugzilla->logout;
return $list;
}
sub create
{
my $self =shift;
my ($new_values) = @_;
Bugzilla->login;
_convert_to_ids($new_values);
my $test_plan = new Bugzilla::Testopia::TestPlan($new_values);
Bugzilla->logout;
# Result is new test plan id
return $test_plan->store();
}
sub update
{
my $self =shift;
my ($test_plan_id, $new_values) = @_;
Bugzilla->login;
my $test_plan = new Bugzilla::Testopia::TestPlan($test_plan_id);
_convert_to_ids($new_values);
$test_plan->update($new_values);
Bugzilla->logout;
# Result is zero on success, otherwise an exception will be thrown
return 0;
}
1;

View File

@@ -0,0 +1,177 @@
# -*- 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 Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Dallas Harken <dharken@novell.com>
package Bugzilla::WebService::Testopia::TestRun;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Util qw(detaint_natural);
use Bugzilla::Product;
use Bugzilla::User;
use Bugzilla::Testopia::TestRun;
use Bugzilla::Testopia::Search;
use Bugzilla::Testopia::Table;
# Convert string field values to their respective integer id's
sub _convert_to_ids
{
my ($hash) = @_;
if (defined($$hash{"manager"}))
{
$$hash{"manager_id"} = login_to_id($$hash{"manager"});
}
delete $$hash{"manager"};
if (defined($$hash{"environment"}))
{
$$hash{"environment_id"} = Bugzilla::Testopia::TestRun::lookup_environment_by_name($$hash{"environment"});
}
delete $$hash{"environment"};
}
# Convert fields with integer id's to their respective string values
sub _convert_to_strings
{
my ($hash) = @_;
$$hash{"manager"} = "";
if (defined($$hash{"manager_id"}))
{
$$hash{"manager"} = new Bugzilla::User($$hash{"manager_id"})->login;
}
delete $$hash{"manager_id"};
$$hash{"environment"} = "";
if (defined($$hash{"environment_id"}))
{
$$hash{"environment"} = Bugzilla::Testopia::TestRun->lookup_environment($$hash{"environment_id"});
}
delete $$hash{"environment_id"};
}
# Utility method called by the list method
sub _list
{
my ($query) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param("viewall", 1);
$cgi->param("current_tab", "run");
foreach (keys(%$query))
{
$cgi->param($_, $$query{$_});
}
my $search = Bugzilla::Testopia::Search->new($cgi);
# Result is an array of test run hash maps
return Bugzilla::Testopia::Table->new('run',
'tr_xmlrpc.cgi',
$cgi,
undef,
$search->query()
)->list();
}
sub get
{
my $self = shift;
my ($test_run_id) = @_;
Bugzilla->login;
# We can detaint immediately if what we get passed is fully numeric.
# We leave bug alias checks to Bugzilla::Testopia::TestRun::new.
if ($test_run_id =~ /^[0-9]+$/) {
detaint_natural($test_run_id);
}
#Result is a test run hash map
my $testrun = new Bugzilla::Testopia::TestRun($test_run_id);
_convert_to_strings($testrun);
Bugzilla->logout;
return $testrun;
}
sub list
{
Bugzilla->login;
my $self = shift;
my ($query) = @_;
_convert_to_ids($query);
my $list = _list($query);
foreach (@$list)
{
_convert_to_strings($_);
}
Bugzilla->logout;
return $list;
}
sub create
{
my $self =shift;
my ($new_values) = @_;
Bugzilla->login;
_convert_to_ids($new_values);
my $test_run = new Bugzilla::Testopia::TestRun($new_values);
Bugzilla->logout;
# Result is new test run id
return $test_run->store();
}
sub update
{
my $self =shift;
my ($test_run_id, $new_values) = @_;
Bugzilla->login;
my $test_run = new Bugzilla::Testopia::TestRun($test_run_id);
_convert_to_ids($new_values);
$test_run->update($new_values);
Bugzilla->logout;
# Result is zero on success, otherwise an exception will be thrown
return 0;
}
1;