584 lines
15 KiB
Perl
584 lines
15 KiB
Perl
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
|
#
|
|
# The contents of this file are subject to the Mozilla Public
|
|
# License Version 1.1 (the "License"); you may not use this file
|
|
# except in compliance with the License. You may obtain a copy of
|
|
# the License at http://www.mozilla.org/MPL/
|
|
#
|
|
# Software distributed under the License is distributed on an "AS
|
|
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
# implied. See the License for the specific language governing
|
|
# rights and limitations under the License.
|
|
#
|
|
# The Original Code is the Bugzilla Testopia System.
|
|
#
|
|
# The Initial Developer of the Original Code is Greg Hendricks.
|
|
# Portions created by Greg Hendricks are Copyright (C) 2006
|
|
# Novell. All Rights Reserved.
|
|
#
|
|
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
|
|
|
|
package Bugzilla::Testopia::Build;
|
|
|
|
use strict;
|
|
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Testopia::Product;
|
|
use JSON;
|
|
|
|
use base qw(Exporter Bugzilla::Object);
|
|
@Bugzilla::Testopia::Build::EXPORT = qw(check_build);
|
|
|
|
###############################
|
|
#### Initialization ####
|
|
###############################
|
|
use constant DB_TABLE => "test_builds";
|
|
use constant NAME_FIELD => "name";
|
|
use constant ID_FIELD => "build_id";
|
|
use constant DB_COLUMNS => qw(
|
|
build_id
|
|
product_id
|
|
name
|
|
description
|
|
milestone
|
|
isactive
|
|
);
|
|
|
|
use constant REQUIRED_CREATE_FIELDS => qw(product_id name milestone isactive);
|
|
use constant UPDATE_COLUMNS => qw(name description milestone isactive);
|
|
|
|
use constant VALIDATORS => {
|
|
product_id => \&_check_product,
|
|
isactive => \&_check_isactive,
|
|
};
|
|
|
|
###############################
|
|
#### Validators ####
|
|
###############################
|
|
sub _check_product {
|
|
my ($invocant, $product_id) = @_;
|
|
$product_id = trim($product_id);
|
|
|
|
ThrowUserError("testopia-create-denied", {'object' => 'build'}) unless Bugzilla->user->in_group('Testers');
|
|
|
|
my $product;
|
|
if (trim($product_id) !~ /^\d+$/ ){
|
|
$product = Bugzilla::Product::check_product($product_id);
|
|
}
|
|
else {
|
|
$product = Bugzilla::Testopia::Product->new($product_id);
|
|
}
|
|
|
|
if (ref $invocant){
|
|
$invocant->{'product'} = $product;
|
|
return $product->id;
|
|
}
|
|
return $product;
|
|
}
|
|
|
|
sub _check_name {
|
|
my ($invocant, $name, $product_id) = @_;
|
|
$name = clean_text($name) if $name;
|
|
|
|
if (!defined $name || $name eq '') {
|
|
ThrowUserError('testopia-missing-required-field', {'field' => 'name'});
|
|
}
|
|
|
|
trick_taint($name);
|
|
|
|
# Check that we don't already have a build with that name in this product.
|
|
my $orig_id = check_build($name, $product_id);
|
|
my $notunique;
|
|
|
|
if (ref $invocant){
|
|
# If updating, we have matched ourself at least
|
|
$notunique = 1 if (($orig_id && $orig_id != $invocant->id))
|
|
}
|
|
else {
|
|
# In new build any match is one too many
|
|
$notunique = 1 if $orig_id;
|
|
}
|
|
|
|
ThrowUserError('testopia-name-not-unique',
|
|
{'object' => 'Build',
|
|
'name' => $name}) if $notunique;
|
|
|
|
return $name;
|
|
}
|
|
|
|
sub _check_milestone {
|
|
my ($invocant, $milestone, $product) = @_;
|
|
if (ref $invocant){
|
|
$product = $invocant->product;
|
|
}
|
|
$milestone = trim($milestone);
|
|
$milestone = Bugzilla::Milestone->check({product => $product, name => $milestone});
|
|
return $milestone->name;
|
|
}
|
|
|
|
sub _check_isactive {
|
|
my ($invocant, $isactive) = @_;
|
|
ThrowCodeError('bad_arg', {argument => 'isactive', function => 'set_isactive'}) unless ($isactive =~ /(1|0)/);
|
|
return $isactive;
|
|
}
|
|
|
|
###############################
|
|
#### Mutators ####
|
|
###############################
|
|
sub set_description { $_[0]->set('description', $_[1]); }
|
|
sub set_isactive { $_[0]->set('isactive', $_[1]); }
|
|
sub set_milestone {
|
|
my ($self, $value) = @_;
|
|
$value = $self->_check_milestone($value);
|
|
$self->set('milestone', $value);
|
|
}
|
|
sub set_name {
|
|
my ($self, $value) = @_;
|
|
|
|
$value = $self->_check_name($value, $self->product);
|
|
$self->set('name', $value);
|
|
}
|
|
|
|
sub new {
|
|
my $invocant = shift;
|
|
my $class = ref($invocant) || $invocant;
|
|
my $param = shift;
|
|
|
|
# We want to be able to supply an empty object to the templates for numerous
|
|
# lists etc. This is much cleaner than exporting a bunch of subroutines and
|
|
# adding them to $vars one by one. Probably just Laziness shining through.
|
|
if (ref $param eq 'HASH'){
|
|
if (!keys %$param || $param->{PREVALIDATED}){
|
|
bless($param, $class);
|
|
return $param;
|
|
}
|
|
}
|
|
|
|
unshift @_, $param;
|
|
my $self = $class->SUPER::new(@_);
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub run_create_validators {
|
|
my $class = shift;
|
|
my $params = $class->SUPER::run_create_validators(@_);
|
|
my $product = $params->{product_id}; # Returns actual product object
|
|
|
|
$params->{milestone} = $class->_check_milestone($params->{milestone}, $product);
|
|
$params->{name} = $class->_check_name($params->{name}, $product);
|
|
|
|
return $params;
|
|
}
|
|
|
|
sub create {
|
|
my ($class, $params) = @_;
|
|
|
|
$class->SUPER::check_required_create_fields($params);
|
|
my $field_values = $class->run_create_validators($params);
|
|
|
|
$field_values->{isactive} = 1;
|
|
$field_values->{product_id} = $field_values->{product_id}->id;
|
|
my $self = $class->SUPER::insert_create_data($field_values);
|
|
|
|
return $self;
|
|
}
|
|
###############################
|
|
#### Functions ####
|
|
###############################
|
|
sub check_build {
|
|
my ($name, $product, $throw) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $is = $dbh->selectrow_array(
|
|
"SELECT build_id FROM test_builds
|
|
WHERE name = ? AND product_id = ?",
|
|
undef, $name, $product->id);
|
|
if ($throw){
|
|
ThrowUserError('invalid-test-id-non-existent', {type => 'Build', id => $name}) unless $is;
|
|
return Bugzilla::Testopia::Build->new($is);
|
|
}
|
|
return $is;
|
|
}
|
|
|
|
###############################
|
|
#### Methods ####
|
|
###############################
|
|
sub TO_JSON {
|
|
my $self = shift;
|
|
my $obj;
|
|
my $json = new JSON;
|
|
|
|
foreach my $field ($self->DB_COLUMNS){
|
|
$obj->{$field} = $self->{$field};
|
|
}
|
|
|
|
return $json->encode($obj);
|
|
}
|
|
|
|
###############################
|
|
#### Accessors ####
|
|
###############################
|
|
sub id { return $_[0]->{'build_id'}; }
|
|
sub product_id { return $_[0]->{'product_id'}; }
|
|
sub name { return $_[0]->{'name'}; }
|
|
sub description { return $_[0]->{'description'};}
|
|
sub milestone { return $_[0]->{'milestone'};}
|
|
sub isactive { return $_[0]->{'isactive'};}
|
|
|
|
sub bugs {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
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
|
|
WHERE r.build_id = ?",
|
|
undef, $self->id);
|
|
my @bugs;
|
|
foreach my $id (@{$ref}){
|
|
push @bugs, Bugzilla::Bug->new($id, Bugzilla->user->id);
|
|
}
|
|
$self->{'bugs'} = \@bugs if @bugs;
|
|
$self->{'bug_list'} = join(',', @$ref);
|
|
return $self->{'bugs'};
|
|
}
|
|
|
|
sub product {
|
|
my ($self) = @_;
|
|
|
|
return $self->{'product'} if exists $self->{'product'};
|
|
|
|
$self->{'product'} = Bugzilla::Testopia::Product->new($self->product_id);
|
|
return $self->{'product'};
|
|
}
|
|
|
|
sub run_count {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'run_count'} if exists $self->{'run_count'};
|
|
|
|
$self->{'run_count'} = $dbh->selectrow_array(
|
|
"SELECT COUNT(run_id) FROM test_runs
|
|
WHERE build_id = ?", undef, $self->{'build_id'});
|
|
|
|
return $self->{'run_count'};
|
|
}
|
|
|
|
sub case_run_count {
|
|
my $self = shift;
|
|
my ($status_id, $builds) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my @build_ids;
|
|
if ($builds){
|
|
push @build_ids, $_->id foreach (@$builds);
|
|
}
|
|
push @build_ids, $self->id if $self->id;
|
|
|
|
my $ids = join (',', @build_ids);
|
|
|
|
my $query = "SELECT COUNT(case_run_id) FROM test_case_runs
|
|
WHERE build_id IN (". $ids . ")";
|
|
$query .= " AND case_run_status_id = ?" if $status_id;
|
|
|
|
my $count;
|
|
if ($status_id){
|
|
$count = $dbh->selectrow_array($query, undef, ($status_id));
|
|
}
|
|
else {
|
|
$count = $dbh->selectrow_array($query);
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
sub runs {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'runs'} if exists $self->{'runs'};
|
|
|
|
require Bugzilla::Testopia::TestRun;
|
|
|
|
my $runids = $dbh->selectcol_arrayref("SELECT run_id FROM test_runs
|
|
WHERE build_id = ?",
|
|
undef, $self->id);
|
|
my @runs;
|
|
foreach my $id (@{$runids}){
|
|
push @runs, Bugzilla::Testopia::TestRun->new($id);
|
|
}
|
|
|
|
$self->{'runs'} = \@runs;
|
|
return $self->{'runs'};
|
|
}
|
|
|
|
=head2 caseruns
|
|
|
|
Returns a reference to a list of test caseruns useing this build
|
|
|
|
=cut
|
|
|
|
sub caseruns {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
return $self->{'caseruns'} if exists $self->{'caseruns'};
|
|
|
|
require Bugzilla::Testopia::TestCaseRun;
|
|
|
|
my $ids = $dbh->selectcol_arrayref("SELECT case_run_id FROM test_case_runs
|
|
WHERE build_id = ?",
|
|
undef, $self->id);
|
|
my @caseruns;
|
|
foreach my $id (@{$ids}){
|
|
push @caseruns, Bugzilla::Testopia::TestCaseRun->new($id);
|
|
}
|
|
|
|
$self->{'caseruns'} = \@caseruns;
|
|
return $self->{'caseruns'};
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Testopia::Build
|
|
|
|
=head1 EXTENDS
|
|
|
|
Bugzilla::Object
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Builds are used to classify test runs. They correspond to the results of
|
|
a period of work in software development. Builds are product level attributes
|
|
and are associated with a milestone if targetmilestones are used in Bugzilla.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
=head2 Creating
|
|
|
|
$build = Bugzilla::Testopia::Build->new($build_id);
|
|
$build = Bugzilla::Testopia::Build->new({name => $name});
|
|
|
|
$new_build = Bugzilla::Testopia::Build->create({name => $name,
|
|
description => $desc
|
|
... });
|
|
|
|
=head2 Updating
|
|
|
|
$build->set_name($name);
|
|
$build->set_description($name);
|
|
$build->set_milestone($milestone);
|
|
$build->set_isactive($isactive);
|
|
|
|
$build->update();
|
|
|
|
=head2 Accessors
|
|
|
|
my $id = $build->id;
|
|
my $name = $build->name;
|
|
my $desc = $build->description;
|
|
my $pid = $build->product_id;
|
|
my $milestone = $build->milestone;
|
|
my $crc = $build->case_run_count;
|
|
my $active = $build->isactive;
|
|
|
|
=head1 FIELDS
|
|
|
|
+-------------+------------------+------+-----+---------+----------------+
|
|
| Field | Type | Null | Key | Default | Extra |
|
|
+-------------+------------------+------+-----+---------+----------------+
|
|
| build_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
|
|
| product_id | smallint(6) | NO | MUL | 0 | |
|
|
| milestone | varchar(20) | YES | MUL | NULL | |
|
|
| name | varchar(255) | YES | MUL | NULL | |
|
|
| description | text | YES | | NULL | |
|
|
| isactive | tinyint(4) | NO | | 1 | |
|
|
+-------------+------------------+------+-----+---------+----------------+
|
|
|
|
=over
|
|
|
|
=item C<build_id>
|
|
|
|
The unique id of this build in the database.
|
|
|
|
=item C<name> B<REQUIRED>
|
|
|
|
A unique name for this build.
|
|
|
|
=item C<product_id> B<REQUIRED> B<CREATE ONLY>
|
|
|
|
The id of the Bugzilla product this build is attached to.
|
|
|
|
=item C<milestone> I<OPTIONAL>
|
|
|
|
The value from the Bugzilla product milestone table this build is associated with.
|
|
Defautlts to the product default milestone.
|
|
|
|
=item C<description> I<OPTIONAL>
|
|
|
|
A description of this build.
|
|
|
|
=item C<isactive> I<OPTIONAL>
|
|
|
|
Boolean - Determines whether to show this build in lists for selection.
|
|
Defaults to true.
|
|
|
|
=back
|
|
|
|
=head1 FUNCTIONS
|
|
|
|
=over
|
|
|
|
=item C<check_build($name, $product_id)>
|
|
|
|
Description: Checks if a build of a given name exists for a given product.
|
|
|
|
Params: name - string representing the name to check for.
|
|
product_id - the product to lookup the build in.
|
|
|
|
Returns: The id of the build if one matches.
|
|
undef if it does not match any build.
|
|
|
|
=back
|
|
|
|
=head1 METHODS
|
|
|
|
=over
|
|
|
|
=item C<new($param)>
|
|
|
|
Description: Used to load an existing build from the database.
|
|
|
|
Params: $param - An integer representing the ID in the database
|
|
or a hash with the "name" key representing the named
|
|
build in the database.
|
|
|
|
Returns: A blessed Bugzilla::Testopia::Build object
|
|
|
|
=item C<create()>
|
|
|
|
Description: Creates a new build object and stores it in the database
|
|
|
|
Params: A hash with keys and values matching the fields of the build to
|
|
be created.
|
|
|
|
Returns: The newly created object.
|
|
|
|
=item C<set_description()>
|
|
|
|
Description: Replaces the current build's description. Must call update to
|
|
store the change in the database.
|
|
|
|
Params: text - the new description.
|
|
|
|
Returns: nothing.
|
|
|
|
=item C<set_isactive()>
|
|
|
|
Description: Sets the isactive field.
|
|
|
|
Params: boolean - 1 for active 0 for inactive.
|
|
|
|
Returns: nothing.
|
|
|
|
=item C<set_milestone()>
|
|
|
|
Description: Assigns this build to a different milestone
|
|
|
|
Params: string - the new milestone value
|
|
|
|
Returns: nothing.
|
|
|
|
=item C<set_name()>
|
|
|
|
Description: Renames the current build. If the new name is already in use
|
|
by another build in this product, an error will be thrown.
|
|
The update method must be called to make the change in the database.
|
|
|
|
Params: string - the new name
|
|
|
|
Returns: nothing.
|
|
|
|
=item C<to_json()>
|
|
|
|
Description: Outputs a JSON representation of the object.
|
|
|
|
Params: none
|
|
|
|
Returns: A JSON string.
|
|
|
|
=back
|
|
|
|
=head1 ACCESSORS
|
|
|
|
=over
|
|
|
|
=item C<case_run_count()>
|
|
|
|
Params: case_run_status_id - optional;
|
|
|
|
Returns: The number of case-runs in this build. Optionally for a given status.
|
|
|
|
=item C<caseruns()>
|
|
|
|
Params: none
|
|
|
|
Returns: A list of blessed caserun objects that use this build
|
|
|
|
=item C<description()>
|
|
|
|
Returns the description of this build.
|
|
|
|
=item C<id()>
|
|
|
|
Returns the id of the build
|
|
|
|
=item C<isactive()>
|
|
|
|
Returns 1 if this build is visible in pick lists for runs and caserund and 0 if not.
|
|
|
|
=item C<milestone()>
|
|
|
|
Returns the milestone value that this build is associated with.
|
|
|
|
=item C<name()>
|
|
|
|
Returns the name of this build
|
|
|
|
=item C<product()>
|
|
|
|
Returns a Bugzilla::Testopia::Product object of the product this build is of.
|
|
|
|
=item C<product_id()>
|
|
|
|
Returns the product id of the build.
|
|
|
|
=item C<run_count()>
|
|
|
|
Returns an integer representing the number of runs this build is associated to.
|
|
|
|
=item C<runs()>
|
|
|
|
Params: none
|
|
|
|
Returns: A list of blessed run objects that use this build
|
|
|
|
=back
|
|
|
|
=head1 SEE ALSO
|
|
|
|
=over
|
|
|
|
L<Bugzilla::Testopia::Product>
|
|
L<Bugzilla::Testopia::TestRun>
|
|
L<Bugzilla::Object>
|
|
|
|
=back
|
|
|
|
=head1 AUTHOR
|
|
|
|
Greg Hendricks <ghendricks@novell.com> |