catch known-broken tests as it's supposed to. r=mkanat, a=mkanat (module owner) git-svn-id: svn://10.0.0.236/trunk@262319 18797224-902f-48f8-a5cc-f745e15eee43
1003 lines
36 KiB
Perl
1003 lines
36 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 Bug Tracking System.
|
|
#
|
|
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
|
# Portions created by the Initial Developer are Copyright (C) 2010 the
|
|
# Initial Developer. All Rights Reserved.
|
|
#
|
|
# Contributor(s):
|
|
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
|
|
# This module tests Bugzilla/Search.pm. It uses various constants
|
|
# that are in Bugzilla::Test::Search::Constants, in xt/lib/.
|
|
#
|
|
# It does this by:
|
|
# 1) Creating a bunch of field values. Each field value is
|
|
# randomly named and fully unique.
|
|
# 2) Creating a bunch of bugs that use those unique field
|
|
# values. Each bug has different characteristics--see
|
|
# the comment above the NUM_BUGS constant for a description
|
|
# of each bug.
|
|
# 3) Running searches using the combination of every search operator against
|
|
# every field. The tests that we run are described by the TESTS constant.
|
|
# Some of the operator/field combinations are known to be broken--
|
|
# these are listed in the KNOWN_BROKEN constant.
|
|
# 4) For each search, we make sure that certain bugs are contained in
|
|
# the search, and certain other bugs are not contained in the search.
|
|
# The code for the operator/field tests is mostly in
|
|
# Bugzilla::Test::Search::FieldTest.
|
|
# 5) After testing each operator/field combination's functionality, we
|
|
# do additional tests to make sure that there are no SQL injections
|
|
# possible via any operator/field combination. The code for the
|
|
# SQL Injection tests is in Bugzilla::Test::Search::InjectionTest.
|
|
#
|
|
# Generally, the only way that you should modify the behavior of this
|
|
# script is by modifying the constants.
|
|
|
|
package Bugzilla::Test::Search;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Bugzilla::Attachment;
|
|
use Bugzilla::Bug ();
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Field;
|
|
use Bugzilla::Field::Choice;
|
|
use Bugzilla::FlagType;
|
|
use Bugzilla::Group;
|
|
use Bugzilla::Install ();
|
|
use Bugzilla::Test::Search::Constants;
|
|
use Bugzilla::Test::Search::CustomTest;
|
|
use Bugzilla::Test::Search::FieldTestNormal;
|
|
use Bugzilla::Test::Search::OperatorTest;
|
|
use Bugzilla::User ();
|
|
use Bugzilla::Util qw(generate_random_password);
|
|
|
|
use Carp;
|
|
use DateTime;
|
|
use Scalar::Util qw(blessed);
|
|
|
|
###############
|
|
# Constructor #
|
|
###############
|
|
|
|
sub new {
|
|
my ($class, $options) = @_;
|
|
return bless { options => $options }, $class;
|
|
}
|
|
|
|
#############
|
|
# Accessors #
|
|
#############
|
|
|
|
sub options { return $_[0]->{options} }
|
|
sub option { return $_[0]->{options}->{$_[1]} }
|
|
|
|
sub num_tests {
|
|
my ($self) = @_;
|
|
my @top_operators = $self->top_level_operators;
|
|
my @all_operators = $self->all_operators;
|
|
my $top_operator_tests = $self->_total_operator_tests(\@top_operators);
|
|
my $all_operator_tests = $self->_total_operator_tests(\@all_operators);
|
|
|
|
my @fields = $self->all_fields;
|
|
|
|
# Basically, we run TESTS_PER_RUN tests for each field/operator combination.
|
|
my $top_combinations = $top_operator_tests * scalar(@fields);
|
|
my $all_combinations = $all_operator_tests * scalar(@fields);
|
|
# But we also have ORs, for which we run combinations^2 tests.
|
|
my $join_tests = $self->option('long')
|
|
? ($top_combinations * $all_combinations) : 0;
|
|
# And AND tests, which means we run 2x $join_tests;
|
|
$join_tests = $join_tests * 2;
|
|
# Also, because of NOT tests and Normal tests, we run 3x $top_combinations.
|
|
my $basic_tests = $top_combinations * 3;
|
|
my $operator_field_tests = ($basic_tests + $join_tests) * TESTS_PER_RUN;
|
|
|
|
# Then we test each field/operator combination for SQL injection.
|
|
my @injection_values = INJECTION_TESTS;
|
|
my $sql_injection_tests = scalar(@fields) * scalar(@top_operators)
|
|
* scalar(@injection_values) * NUM_SEARCH_TESTS;
|
|
|
|
# This @{ [] } thing is the only reasonable way to get a count out of a
|
|
# constant array.
|
|
my $special_tests = scalar(@{ [SPECIAL_PARAM_TESTS, CUSTOM_SEARCH_TESTS] })
|
|
* TESTS_PER_RUN;
|
|
|
|
return $operator_field_tests + $sql_injection_tests + $special_tests;
|
|
}
|
|
|
|
sub _total_operator_tests {
|
|
my ($self, $operators) = @_;
|
|
|
|
# Some operators have more than one test. Find those ones and add
|
|
# them to the total operator tests
|
|
my $extra_operator_tests;
|
|
foreach my $operator (@$operators) {
|
|
my $tests = TESTS->{$operator};
|
|
next if !$tests;
|
|
my $extra_num = scalar(@$tests) - 1;
|
|
$extra_operator_tests += $extra_num;
|
|
}
|
|
return scalar(@$operators) + $extra_operator_tests;
|
|
|
|
}
|
|
|
|
sub all_operators {
|
|
my ($self) = @_;
|
|
if (not $self->{all_operators}) {
|
|
|
|
my @operators;
|
|
if (my $limit_operators = $self->option('operators')) {
|
|
@operators = split(',', $limit_operators);
|
|
}
|
|
else {
|
|
@operators = sort (keys %{ Bugzilla::Search::OPERATORS() });
|
|
}
|
|
# "substr" is just a backwards-compatibility operator, same as "substring".
|
|
@operators = grep { $_ ne 'substr' } @operators;
|
|
$self->{all_operators} = \@operators;
|
|
}
|
|
return @{ $self->{all_operators} };
|
|
}
|
|
|
|
sub all_fields {
|
|
my $self = shift;
|
|
if (not $self->{all_fields}) {
|
|
$self->_create_custom_fields();
|
|
my @fields = @{ Bugzilla->fields };
|
|
@fields = sort { $a->name cmp $b->name } @fields;
|
|
$self->{all_fields} = \@fields;
|
|
}
|
|
return @{ $self->{all_fields} };
|
|
}
|
|
|
|
sub top_level_operators {
|
|
my ($self) = @_;
|
|
if (!$self->{top_level_operators}) {
|
|
my @operators;
|
|
my $limit_top = $self->option('top-operators');
|
|
if ($limit_top) {
|
|
@operators = split(',', $limit_top);
|
|
}
|
|
else {
|
|
@operators = $self->all_operators;
|
|
}
|
|
$self->{top_level_operators} = \@operators;
|
|
}
|
|
return @{ $self->{top_level_operators} };
|
|
}
|
|
|
|
sub text_fields {
|
|
my ($self) = @_;
|
|
my @text_fields = grep { $_->type == FIELD_TYPE_TEXTAREA
|
|
or $_->type == FIELD_TYPE_FREETEXT } $self->all_fields;
|
|
@text_fields = map { $_->name } @text_fields;
|
|
push(@text_fields, qw(short_desc status_whiteboard bug_file_loc see_also));
|
|
return @text_fields;
|
|
}
|
|
|
|
sub bugs {
|
|
my $self = shift;
|
|
$self->{bugs} ||= [map { $self->_create_one_bug($_) } (1..NUM_BUGS)];
|
|
return @{ $self->{bugs} };
|
|
}
|
|
|
|
# Get a numbered bug.
|
|
sub bug {
|
|
my ($self, $number) = @_;
|
|
return ($self->bugs)[$number - 1];
|
|
}
|
|
|
|
sub admin {
|
|
my $self = shift;
|
|
if (!$self->{admin_user}) {
|
|
my $admin = create_user("admin");
|
|
Bugzilla::Install::make_admin($admin);
|
|
$self->{admin_user} = $admin;
|
|
}
|
|
# We send back a fresh object every time, to make sure that group
|
|
# memberships are always up-to-date.
|
|
return new Bugzilla::User($self->{admin_user}->id);
|
|
}
|
|
|
|
sub nobody {
|
|
my $self = shift;
|
|
$self->{nobody} ||= Bugzilla::Group->create({ name => "nobody-" . random(),
|
|
description => "Nobody", isbuggroup => 1 });
|
|
return $self->{nobody};
|
|
}
|
|
sub everybody {
|
|
my ($self) = @_;
|
|
$self->{everybody} ||= create_group('To The Limit');
|
|
return $self->{everybody};
|
|
}
|
|
|
|
sub bug_create_value {
|
|
my ($self, $number, $field) = @_;
|
|
$field = $field->name if blessed($field);
|
|
if ($number == 6 and $field ne 'alias') {
|
|
$number = 1;
|
|
}
|
|
my $extra_values = $self->_extra_bug_create_values->{$number};
|
|
if (exists $extra_values->{$field}) {
|
|
return $extra_values->{$field};
|
|
}
|
|
return $self->_bug_create_values->{$number}->{$field};
|
|
}
|
|
sub bug_update_value {
|
|
my ($self, $number, $field) = @_;
|
|
$field = $field->name if blessed($field);
|
|
if ($number == 6 and $field ne 'alias') {
|
|
$number = 1;
|
|
}
|
|
return $self->_bug_update_values->{$number}->{$field};
|
|
}
|
|
|
|
# Values used to create the bugs.
|
|
sub _bug_create_values {
|
|
my $self = shift;
|
|
return $self->{bug_create_values} if $self->{bug_create_values};
|
|
my %values;
|
|
foreach my $number (1..NUM_BUGS) {
|
|
$values{$number} = $self->_create_field_values($number, 'for create');
|
|
}
|
|
$self->{bug_create_values} = \%values;
|
|
return $self->{bug_create_values};
|
|
}
|
|
# Values as they existed on the bug, at creation time. Used by the
|
|
# changedfrom tests.
|
|
sub _extra_bug_create_values {
|
|
my $self = shift;
|
|
$self->{extra_bug_create_values} ||= { map { $_ => {} } (1..NUM_BUGS) };
|
|
return $self->{extra_bug_create_values};
|
|
}
|
|
|
|
# Values used to update the bugs after they are created.
|
|
sub _bug_update_values {
|
|
my $self = shift;
|
|
return $self->{bug_update_values} if $self->{bug_update_values};
|
|
my %values;
|
|
foreach my $number (1..NUM_BUGS) {
|
|
$values{$number} = $self->_create_field_values($number);
|
|
}
|
|
$self->{bug_update_values} = \%values;
|
|
return $self->{bug_update_values};
|
|
}
|
|
|
|
##############################
|
|
# General Helper Subroutines #
|
|
##############################
|
|
|
|
sub random {
|
|
$_[0] ||= FIELD_SIZE;
|
|
generate_random_password(@_);
|
|
}
|
|
|
|
# We need to use a custom timestamp for each create() and update(),
|
|
# because the database returns the same value for LOCALTIMESTAMP(0)
|
|
# for the entire transaction, and we need each created bug to have
|
|
# its own creation_ts and delta_ts.
|
|
sub timestamp {
|
|
my ($day, $second) = @_;
|
|
return DateTime->new(
|
|
year => 2037,
|
|
month => 1,
|
|
day => $day,
|
|
hour => 12,
|
|
minute => $second,
|
|
second => 0,
|
|
# We make it floating because the timezone doesn't matter for our uses,
|
|
# and we want totally consistent behavior across all possible machines.
|
|
time_zone => 'floating',
|
|
);
|
|
}
|
|
|
|
sub create_keyword {
|
|
my ($number) = @_;
|
|
return Bugzilla::Keyword->create({
|
|
name => "$number-keyword-" . random(),
|
|
description => "Keyword $number" });
|
|
}
|
|
|
|
sub create_user {
|
|
my ($prefix) = @_;
|
|
my $user_name = $prefix . '-' . random(15) . "@" . random(12)
|
|
. "." . random(3);
|
|
my $user_realname = $prefix . '-' . random();
|
|
my $user = Bugzilla::User->create({
|
|
login_name => $user_name,
|
|
realname => $user_realname,
|
|
cryptpassword => '*',
|
|
});
|
|
return $user;
|
|
}
|
|
|
|
sub create_group {
|
|
my ($prefix) = @_;
|
|
return Bugzilla::Group->create({
|
|
name => "$prefix-group-" . random(), description => "Everybody $prefix",
|
|
userregexp => '.*', isbuggroup => 1 });
|
|
}
|
|
|
|
sub create_legal_value {
|
|
my ($field, $number) = @_;
|
|
my $type = Bugzilla::Field::Choice->type($field);
|
|
my $field_name = $field->name;
|
|
return $type->create({ value => "$number-$field_name-" . random(),
|
|
is_open => 0 });
|
|
}
|
|
|
|
#########################
|
|
# Custom Field Creation #
|
|
#########################
|
|
|
|
sub _create_custom_fields {
|
|
my ($self) = @_;
|
|
return if !$self->option('add-custom-fields');
|
|
|
|
while (my ($type, $name) = each %{ CUSTOM_FIELDS() }) {
|
|
my $exists = new Bugzilla::Field({ name => $name });
|
|
next if $exists;
|
|
Bugzilla::Field->create({
|
|
name => $name,
|
|
type => $type,
|
|
description => "Search Test Field $name",
|
|
enter_bug => 1,
|
|
custom => 1,
|
|
buglist => 1,
|
|
is_mandatory => 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
########################
|
|
# Field Value Creation #
|
|
########################
|
|
|
|
sub _create_field_values {
|
|
my ($self, $number, $for_create) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
Bugzilla->set_user($self->admin);
|
|
|
|
my @selects = grep { $_->is_select } $self->all_fields;
|
|
my %values;
|
|
foreach my $field (@selects) {
|
|
next if $field->is_abnormal;
|
|
$values{$field->name} = create_legal_value($field, $number)->name;
|
|
}
|
|
|
|
my $group = create_group($number);
|
|
$values{groups} = [$group->name];
|
|
|
|
$values{'keywords'} = create_keyword($number)->name;
|
|
|
|
foreach my $field (qw(assigned_to qa_contact reporter cc)) {
|
|
$values{$field} = create_user("$number-$field")->login;
|
|
}
|
|
|
|
my $classification = Bugzilla::Classification->create(
|
|
{ name => "$number-classification-" . random() });
|
|
$classification = $classification->name;
|
|
|
|
my $version = "$number-version-" . random();
|
|
my $milestone = "$number-tm-" . random(15);
|
|
my $product = Bugzilla::Product->create({
|
|
name => "$number-product-" . random(),
|
|
description => 'Created by t/search.t',
|
|
defaultmilestone => $milestone,
|
|
classification => $classification,
|
|
version => $version,
|
|
allows_unconfirmed => 1,
|
|
});
|
|
foreach my $item ($group, $self->nobody) {
|
|
$product->set_group_controls($item,
|
|
{ membercontrol => CONTROLMAPSHOWN,
|
|
othercontrol => CONTROLMAPNA });
|
|
}
|
|
# $product->update() is called lower down.
|
|
my $component = Bugzilla::Component->create({
|
|
product => $product, name => "$number-component-" . random(),
|
|
initialowner => create_user("$number-defaultowner")->login,
|
|
initialqacontact => create_user("$number-defaultqa")->login,
|
|
initial_cc => [create_user("$number-initcc")->login],
|
|
description => "Component $number" });
|
|
|
|
$values{'product'} = $product->name;
|
|
$values{'component'} = $component->name;
|
|
$values{'target_milestone'} = $milestone;
|
|
$values{'version'} = $version;
|
|
|
|
foreach my $field ($self->text_fields) {
|
|
# We don't add a - after $field for the text fields, because
|
|
# if we do, fulltext searching for short_desc pulls out
|
|
# "short_desc" as a word and matches it in every bug.
|
|
my $value = "$number-$field" . random();
|
|
if ($field eq 'bug_file_loc' or $field eq 'see_also') {
|
|
$value = "http://$value-" . random(3)
|
|
. "/show_bug.cgi?id=$number";
|
|
}
|
|
$values{$field} = $value;
|
|
}
|
|
$values{'tag'} = ["$number-tag-" . random()];
|
|
|
|
my @date_fields = grep { $_->type == FIELD_TYPE_DATETIME } $self->all_fields;
|
|
foreach my $field (@date_fields) {
|
|
# We use 03 as the month because that differs from our creation_ts,
|
|
# delta_ts, and deadline. (It's nice to have recognizable values
|
|
# for each field when debugging.)
|
|
my $second = $for_create ? $number : $number + 1;
|
|
$values{$field->name} = "2037-03-0$number 12:34:0$second";
|
|
}
|
|
|
|
$values{alias} = "$number-alias-" . random(12);
|
|
|
|
# Prefixing the original comment with "description" makes the
|
|
# lesserthan and greaterthan tests behave predictably.
|
|
my $comm_prefix = $for_create ? "description-" : '';
|
|
$values{comment} = "$comm_prefix$number-comment-" . random()
|
|
. ' ' . random();
|
|
|
|
my @flags;
|
|
my $setter = create_user("$number-setters.login_name");
|
|
my $requestee = create_user("$number-requestees.login_name");
|
|
$values{set_flags} = _create_flags($number, $setter, $requestee);
|
|
|
|
my $month = $for_create ? "12" : "02";
|
|
$values{'deadline'} = "2037-$month-0$number";
|
|
my $estimate_times = $for_create ? 10 : 1;
|
|
$values{estimated_time} = $estimate_times * $number;
|
|
|
|
$values{attachment} = _get_attach_values($number, $for_create);
|
|
|
|
# Some things only happen on the first bug.
|
|
if ($number == 1) {
|
|
# We use 6 as the prefix for the extra values, because bug 6's values
|
|
# don't otherwise get used (since bug 6 is created as a clone of
|
|
# bug 1). This also makes sure that our greaterthan/lessthan
|
|
# tests work properly.
|
|
my $extra_group = create_group(6);
|
|
$product->set_group_controls($extra_group,
|
|
{ membercontrol => CONTROLMAPSHOWN,
|
|
othercontrol => CONTROLMAPNA });
|
|
$values{groups} = [$values{groups}->[0], $extra_group->name];
|
|
my $extra_keyword = create_keyword(6);
|
|
$values{keywords} = [$values{keywords}, $extra_keyword->name];
|
|
my $extra_cc = create_user("6-cc");
|
|
$values{cc} = [$values{cc}, $extra_cc->login];
|
|
my @multi_selects = grep { $_->type == FIELD_TYPE_MULTI_SELECT }
|
|
$self->all_fields;
|
|
foreach my $field (@multi_selects) {
|
|
my $new_value = create_legal_value($field, 6);
|
|
my $name = $field->name;
|
|
$values{$name} = [$values{$name}, $new_value->name];
|
|
}
|
|
push(@{ $values{'tag'} }, "6-tag-" . random());
|
|
}
|
|
|
|
# On bug 5, any field that *can* be left empty, *is* left empty.
|
|
if ($number == 5) {
|
|
my @set_fields = grep { $_->type == FIELD_TYPE_SINGLE_SELECT }
|
|
$self->all_fields;
|
|
@set_fields = map { $_->name } @set_fields;
|
|
push(@set_fields, qw(short_desc version reporter));
|
|
foreach my $key (keys %values) {
|
|
delete $values{$key} unless grep { $_ eq $key } @set_fields;
|
|
}
|
|
}
|
|
|
|
$product->update();
|
|
|
|
return \%values;
|
|
}
|
|
|
|
# Flags
|
|
sub _create_flags {
|
|
my ($number, $setter, $requestee) = @_;
|
|
|
|
my $flagtypes = _create_flagtypes($number);
|
|
|
|
my %flags;
|
|
foreach my $type (qw(a b)) {
|
|
$flags{$type} = _get_flag_values(@_, $flagtypes->{$type});
|
|
}
|
|
return \%flags;
|
|
}
|
|
|
|
sub _create_flagtypes {
|
|
my ($number) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $name = "$number-flag-" . random();
|
|
my $desc = "FlagType $number";
|
|
|
|
my %flagtypes;
|
|
foreach my $target (qw(a b)) {
|
|
$dbh->do("INSERT INTO flagtypes
|
|
(name, description, target_type, is_requestable,
|
|
is_requesteeble, is_multiplicable, cc_list)
|
|
VALUES (?,?,?,1,1,1,'')",
|
|
undef, $name, $desc, $target);
|
|
my $id = $dbh->bz_last_key('flagtypes', 'id');
|
|
$dbh->do('INSERT INTO flaginclusions (type_id) VALUES (?)',
|
|
undef, $id);
|
|
my $flagtype = new Bugzilla::FlagType($id);
|
|
$flagtypes{$target} = $flagtype;
|
|
}
|
|
return \%flagtypes;
|
|
}
|
|
|
|
sub _get_flag_values {
|
|
my ($number, $setter, $requestee, $flagtype) = @_;
|
|
|
|
my @set_flags;
|
|
if ($number <= 2) {
|
|
foreach my $value (qw(? - + ?)) {
|
|
my $flag = { type_id => $flagtype->id, status => $value,
|
|
setter => $setter, flagtype => $flagtype };
|
|
push(@set_flags, $flag);
|
|
}
|
|
$set_flags[0]->{requestee} = $requestee->login;
|
|
}
|
|
else {
|
|
@set_flags = ({ type_id => $flagtype->id, status => '+',
|
|
setter => $setter, flagtype => $flagtype });
|
|
}
|
|
return \@set_flags;
|
|
}
|
|
|
|
# Attachments
|
|
sub _get_attach_values {
|
|
my ($number, $for_create) = @_;
|
|
|
|
my $boolean = $number == 1 ? 1 : 0;
|
|
if ($for_create) {
|
|
$boolean = !$boolean ? 1 : 0;
|
|
}
|
|
my $ispatch = $for_create ? 'ispatch' : 'is_patch';
|
|
my $isobsolete = $for_create ? 'isobsolete' : 'is_obsolete';
|
|
my $isprivate = $for_create ? 'isprivate' : 'is_private';
|
|
my $mimetype = $for_create ? 'mimetype' : 'content_type';
|
|
|
|
my %values = (
|
|
description => "$number-attach_desc-" . random(),
|
|
filename => "$number-filename-" . random(),
|
|
$ispatch => $boolean,
|
|
$isobsolete => $boolean,
|
|
$isprivate => $boolean,
|
|
$mimetype => "text/x-$number-" . random(),
|
|
);
|
|
if ($for_create) {
|
|
$values{data} = "$number-data-" . random() . random();
|
|
}
|
|
return \%values;
|
|
}
|
|
|
|
################
|
|
# Bug Creation #
|
|
################
|
|
|
|
sub _create_one_bug {
|
|
my ($self, $number) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# We need bug 6 to have a unique alias that is not a clone of bug 1's,
|
|
# so we get the alias separately from the other parameters.
|
|
my $alias = $self->bug_create_value($number, 'alias');
|
|
my $update_alias = $self->bug_update_value($number, 'alias');
|
|
|
|
# Otherwise, make bug 6 a clone of bug 1.
|
|
my $real_number = $number;
|
|
$number = 1 if $number == 6;
|
|
|
|
my $reporter = $self->bug_create_value($number, 'reporter');
|
|
Bugzilla->set_user(Bugzilla::User->check($reporter));
|
|
|
|
# We create the bug with one set of values, and then we change it
|
|
# to have different values.
|
|
my %params = %{ $self->_bug_create_values->{$number} };
|
|
$params{alias} = $alias;
|
|
|
|
# There are some things in bug_create_values that shouldn't go into
|
|
# create().
|
|
delete @params{qw(attachment set_flags tag)};
|
|
|
|
my ($status, $resolution, $see_also) =
|
|
delete @params{qw(bug_status resolution see_also)};
|
|
# All the bugs are created with everconfirmed = 0.
|
|
$params{bug_status} = 'UNCONFIRMED';
|
|
my $bug = Bugzilla::Bug->create(\%params);
|
|
|
|
# These are necessary for the changedfrom tests.
|
|
my $extra_values = $self->_extra_bug_create_values->{$number};
|
|
foreach my $field (qw(comments remaining_time percentage_complete
|
|
keyword_objects everconfirmed dependson blocked
|
|
groups_in classification actual_time))
|
|
{
|
|
$extra_values->{$field} = $bug->$field;
|
|
}
|
|
$extra_values->{reporter_accessible} = $number == 1 ? 0 : 1;
|
|
$extra_values->{cclist_accessible} = $number == 1 ? 0 : 1;
|
|
|
|
if ($number == 5) {
|
|
# Bypass Bugzilla::Bug--we don't want any changes in bugs_activity
|
|
# for bug 5.
|
|
$dbh->do('UPDATE bugs SET qa_contact = NULL, reporter_accessible = 0,
|
|
cclist_accessible = 0 WHERE bug_id = ?',
|
|
undef, $bug->id);
|
|
$dbh->do('DELETE FROM cc WHERE bug_id = ?', undef, $bug->id);
|
|
my $ts = '1970-01-01 00:00:00';
|
|
$dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
|
|
WHERE bug_id = ?', undef, $ts, $ts, $bug->id);
|
|
$dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
|
|
undef, $ts, $bug->id);
|
|
$bug->{creation_ts} = $ts;
|
|
$extra_values->{see_also} = [];
|
|
}
|
|
else {
|
|
# Manually set the creation_ts so that each bug has a different one.
|
|
#
|
|
# Also, manually update the resolution and bug_status, because
|
|
# we want to see both of them change in bugs_activity, so we
|
|
# have to start with values for both (and as of the time when I'm
|
|
# writing this test, Bug->create doesn't support setting resolution).
|
|
#
|
|
# Same for see_also.
|
|
my $timestamp = timestamp($number, $number - 1);
|
|
my $creation_ts = $timestamp->ymd . ' ' . $timestamp->hms;
|
|
$bug->{creation_ts} = $creation_ts;
|
|
$dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
|
|
undef, $creation_ts, $bug->id);
|
|
$dbh->do('UPDATE bugs SET creation_ts = ?, bug_status = ?,
|
|
resolution = ? WHERE bug_id = ?',
|
|
undef, $creation_ts, $status, $resolution, $bug->id);
|
|
$dbh->do('INSERT INTO bug_see_also (bug_id, value, class) VALUES (?,?,?)',
|
|
undef, $bug->id, $see_also, 'Bugzilla::BugUrl::Bugzilla');
|
|
$extra_values->{see_also} = $bug->see_also;
|
|
|
|
# All the tags must be created as the admin user, so that the
|
|
# admin user can find them, later.
|
|
my $original_user = Bugzilla->user;
|
|
Bugzilla->set_user($self->admin);
|
|
my $tags = $self->bug_create_value($number, 'tag');
|
|
$bug->add_tag($_) foreach @$tags;
|
|
$extra_values->{tags} = $tags;
|
|
Bugzilla->set_user($original_user);
|
|
|
|
if ($number == 1) {
|
|
# Bug 1 needs to start off with reporter_accessible and
|
|
# cclist_accessible being 0, so that when we change them to 1,
|
|
# that change shows up in bugs_activity.
|
|
$dbh->do('UPDATE bugs SET reporter_accessible = 0,
|
|
cclist_accessible = 0 WHERE bug_id = ?',
|
|
undef, $bug->id);
|
|
# Bug 1 gets three comments, so that longdescs.count matches it
|
|
# uniquely. The third comment is added in the middle, so that the
|
|
# last comment contains all of the important data, like work_time.
|
|
$bug->add_comment("1-comment-" . random(100));
|
|
}
|
|
|
|
my %update_params = %{ $self->_bug_update_values->{$number} };
|
|
my %reverse_map = reverse %{ Bugzilla::Bug->FIELD_MAP };
|
|
foreach my $db_name (keys %reverse_map) {
|
|
next if $db_name eq 'comment';
|
|
next if $db_name eq 'status_whiteboard';
|
|
if (exists $update_params{$db_name}) {
|
|
my $update_name = $reverse_map{$db_name};
|
|
$update_params{$update_name} = delete $update_params{$db_name};
|
|
}
|
|
}
|
|
|
|
my ($new_status, $new_res) =
|
|
delete @update_params{qw(status resolution)};
|
|
# Bypass the status workflow.
|
|
$bug->{bug_status} = $new_status;
|
|
$bug->{resolution} = $new_res;
|
|
$bug->{everconfirmed} = 1 if $number == 1;
|
|
|
|
# add/remove/set fields.
|
|
$update_params{keywords} = { set => $update_params{keywords} };
|
|
$update_params{groups} = { add => $update_params{groups},
|
|
remove => $bug->groups_in };
|
|
my @cc_remove = map { $_->login } @{ $bug->cc_users };
|
|
my $cc_new = $update_params{cc};
|
|
my @cc_add = ref($cc_new) ? @$cc_new : ($cc_new);
|
|
# We make the admin an explicit CC on bug 1 (but not on bug 6), so
|
|
# that we can test the %user% pronoun properly.
|
|
if ($real_number == 1) {
|
|
push(@cc_add, $self->admin->login);
|
|
}
|
|
$update_params{cc} = { add => \@cc_add, remove => \@cc_remove };
|
|
my $see_also_remove = $bug->see_also;
|
|
my $see_also_add = [$update_params{see_also}];
|
|
$update_params{see_also} = { add => $see_also_add,
|
|
remove => $see_also_remove };
|
|
$update_params{comment} = { body => $update_params{comment} };
|
|
$update_params{work_time} = $number;
|
|
# Setting work_time kills the remaining_time, so we need to
|
|
# preserve that. We add 8 because that produces an integer
|
|
# percentage_complete for bug 1, which is necessary for
|
|
# accurate "equals"-type searching.
|
|
$update_params{remaining_time} = $number + 8;
|
|
$update_params{reporter_accessible} = $number == 1 ? 1 : 0;
|
|
$update_params{cclist_accessible} = $number == 1 ? 1 : 0;
|
|
$update_params{alias} = $update_alias;
|
|
|
|
$bug->set_all(\%update_params);
|
|
my $flags = $self->bug_create_value($number, 'set_flags')->{b};
|
|
$bug->set_flags([], $flags);
|
|
$timestamp->set(second => $number);
|
|
$bug->update($timestamp->ymd . ' ' . $timestamp->hms);
|
|
$extra_values->{flags} = $bug->flags;
|
|
|
|
# It's not generally safe to do update() multiple times on
|
|
# the same Bug object.
|
|
$bug = new Bugzilla::Bug($bug->id);
|
|
my $update_flags = $self->bug_update_value($number, 'set_flags')->{b};
|
|
$_->{status} = 'X' foreach @{ $bug->flags };
|
|
$bug->set_flags($bug->flags, $update_flags);
|
|
if ($number == 1) {
|
|
my $comment_id = $bug->comments->[-1]->id;
|
|
$bug->set_comment_is_private({ $comment_id => 1 });
|
|
}
|
|
$bug->update($bug->delta_ts);
|
|
|
|
my $attach_create = $self->bug_create_value($number, 'attachment');
|
|
my $attachment = Bugzilla::Attachment->create({
|
|
bug => $bug,
|
|
creation_ts => $creation_ts,
|
|
%$attach_create });
|
|
# Store for the changedfrom tests.
|
|
$extra_values->{attachments} =
|
|
[new Bugzilla::Attachment($attachment->id)];
|
|
|
|
my $attach_update = $self->bug_update_value($number, 'attachment');
|
|
$attachment->set_all($attach_update);
|
|
# In order to keep the mimetype on the ispatch attachment,
|
|
# we need to bypass the validator.
|
|
$attachment->{mimetype} = $attach_update->{content_type};
|
|
my $attach_flags = $self->bug_update_value($number, 'set_flags')->{a};
|
|
$attachment->set_flags([], $attach_flags);
|
|
$attachment->update($bug->delta_ts);
|
|
}
|
|
|
|
# Values for changedfrom.
|
|
$extra_values->{creation_ts} = $bug->creation_ts;
|
|
$extra_values->{delta_ts} = $bug->creation_ts;
|
|
|
|
return new Bugzilla::Bug($bug->id);
|
|
}
|
|
|
|
###################################
|
|
# Test::Builder Memory Efficiency #
|
|
###################################
|
|
|
|
# Test::Builder stores information for each test run, but Test::Harness
|
|
# and TAP::Harness don't actually need this information. When we run 60
|
|
# million tests, the history eats up all our memory. (After about
|
|
# 1 million tests, memory usage is around 1 GB.)
|
|
#
|
|
# The only part of the history that Test::More actually *uses* is the "ok"
|
|
# field, which we store more efficiently, in an array, and then we re-populate
|
|
# the Test_Results in Test::Builder at the end of the test.
|
|
sub clean_test_history {
|
|
my ($self) = @_;
|
|
return if !$self->option('long');
|
|
my $builder = Test::More->builder;
|
|
my $current_test = $builder->current_test;
|
|
|
|
# I don't use details() because I don't want to copy the array.
|
|
my $results = $builder->{Test_Results};
|
|
my $check_test = $current_test - 1;
|
|
while (my $result = $results->[$check_test]) {
|
|
last if !$result;
|
|
$self->test_success($check_test, $result->{ok});
|
|
$check_test--;
|
|
}
|
|
|
|
# Truncate the test history array, but retain the current test number.
|
|
$builder->{Test_Results} = [];
|
|
$builder->{Curr_Test} = $current_test;
|
|
}
|
|
|
|
sub test_success {
|
|
my ($self, $index, $status) = @_;
|
|
$self->{test_success}->[$index] = $status;
|
|
return $self->{test_success};
|
|
}
|
|
|
|
sub repopulate_test_results {
|
|
my ($self) = @_;
|
|
return if !$self->option('long');
|
|
$self->clean_test_history();
|
|
# We create only two hashes, for memory efficiency.
|
|
my %ok = ( ok => 1 );
|
|
my %not_ok = ( ok => 0 );
|
|
my @results;
|
|
foreach my $success (@{ $self->{test_success} }) {
|
|
push(@results, $success ? \%ok : \%not_ok);
|
|
}
|
|
my $builder = Test::More->builder;
|
|
$builder->{Test_Results} = \@results;
|
|
}
|
|
|
|
##########
|
|
# Caches #
|
|
##########
|
|
|
|
# When doing AND and OR tests, we essentially test the same field/operator
|
|
# combinations over and over. So, if we're going to be running those tests,
|
|
# we cache the translated_value of the FieldTests globally so that we don't
|
|
# have to re-run the value-translation code every time (which can be pretty
|
|
# slow).
|
|
sub value_translation_cache {
|
|
my ($self, $field_test, $value) = @_;
|
|
return if !$self->option('long');
|
|
my $test_name = $field_test->name;
|
|
if (@_ == 3) {
|
|
$self->{value_translation_cache}->{$test_name} = $value;
|
|
}
|
|
return $self->{value_translation_cache}->{$test_name};
|
|
}
|
|
|
|
# When doing AND/OR tests, the value for transformed_value_was_equal
|
|
# (see Bugzilla::Test::Search::FieldTest) won't be recalculated
|
|
# if we pull our values from the value_translation_cache. So we need
|
|
# to also cache the values for transformed_value_was_equal.
|
|
sub was_equal_cache {
|
|
my ($self, $field_test, $number, $value) = @_;
|
|
return if !$self->option('long');
|
|
my $test_name = $field_test->name;
|
|
if (@_ == 4) {
|
|
$self->{tvwe_cache}->{$test_name}->{$number} = $value;
|
|
}
|
|
return $self->{tvwe_cache}->{$test_name}->{$number};
|
|
}
|
|
|
|
#############
|
|
# Main Test #
|
|
#############
|
|
|
|
sub run {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# We want backtraces on any "die" message or any warning.
|
|
# Otherwise it's hard to trace errors inside of Bugzilla::Search from
|
|
# reading automated test run results.
|
|
local $SIG{__WARN__} = \&Carp::cluck;
|
|
local $SIG{__DIE__} = \&Carp::confess;
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
# Some parameters need to be set in order for the tests to function
|
|
# properly.
|
|
my $everybody = $self->everybody;
|
|
my $params = Bugzilla->params;
|
|
local $params->{'useclassification'} = 1;
|
|
local $params->{'useqacontact'} = 1;
|
|
local $params->{'usebugaliases'} = 1;
|
|
local $params->{'usetargetmilestone'} = 1;
|
|
local $params->{'mail_delivery_method'} = 'None';
|
|
local $params->{'timetrackinggroup'} = $everybody->name;
|
|
local $params->{'insidergroup'} = $everybody->name;
|
|
|
|
$self->_setup_bugs();
|
|
|
|
# Even though _setup_bugs set us as an admin, we want to be sure at
|
|
# this point that we have an admin with refreshed group memberships.
|
|
Bugzilla->set_user($self->admin);
|
|
foreach my $test (CUSTOM_SEARCH_TESTS) {
|
|
my $custom_test = new Bugzilla::Test::Search::CustomTest($test, $self);
|
|
$custom_test->run();
|
|
}
|
|
foreach my $test (SPECIAL_PARAM_TESTS) {
|
|
my $operator_test =
|
|
new Bugzilla::Test::Search::OperatorTest($test->{operator}, $self);
|
|
my $field = Bugzilla::Field->check($test->{field});
|
|
my $special_test = new Bugzilla::Test::Search::FieldTestNormal(
|
|
$operator_test, $field, $test);
|
|
$special_test->run();
|
|
}
|
|
foreach my $operator ($self->top_level_operators) {
|
|
my $operator_test =
|
|
new Bugzilla::Test::Search::OperatorTest($operator, $self);
|
|
$operator_test->run();
|
|
}
|
|
|
|
# Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
|
|
my @bug_ids = map { $_->id } $self->bugs;
|
|
my $bug_id_string = join(',', @bug_ids);
|
|
$dbh->do("DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)");
|
|
$dbh->bz_rollback_transaction();
|
|
$self->repopulate_test_results();
|
|
}
|
|
|
|
# This makes a few changes to the bugs after they're created--changes
|
|
# that can only be done after all the bugs have been created.
|
|
sub _setup_bugs {
|
|
my ($self) = @_;
|
|
$self->_setup_dependencies();
|
|
$self->_set_bug_id_fields();
|
|
$self->_protect_bug_6();
|
|
}
|
|
sub _setup_dependencies {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
# Set up depedency relationships between the bugs.
|
|
# Bug 1 + 6 depend on bug 2 and block bug 3.
|
|
my $bug2 = $self->bug(2);
|
|
my $bug3 = $self->bug(3);
|
|
foreach my $number (1,6) {
|
|
my $bug = $self->bug($number);
|
|
my @original_delta = ($bug2->delta_ts, $bug3->delta_ts);
|
|
Bugzilla->set_user($bug->reporter);
|
|
$bug->set_dependencies([$bug2->id], [$bug3->id]);
|
|
$bug->update($bug->delta_ts);
|
|
# Setting dependencies changed the delta_ts on bug2 and bug3, so
|
|
# re-set them back to what they were before. However, we leave
|
|
# the correct update times in bugs_activity, so that the changed*
|
|
# searches still work right.
|
|
my $set_delta = $dbh->prepare(
|
|
'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
|
|
foreach my $row ([$original_delta[0], $bug2->id],
|
|
[$original_delta[1], $bug3->id])
|
|
{
|
|
$set_delta->execute(@$row);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _set_bug_id_fields {
|
|
my ($self) = @_;
|
|
# BUG_ID fields couldn't be set before, because before we create bug 1,
|
|
# we don't necessarily have any valid bug ids.)
|
|
my @bug_id_fields = grep { $_->type == FIELD_TYPE_BUG_ID }
|
|
$self->all_fields;
|
|
foreach my $number (1..NUM_BUGS) {
|
|
my $bug = $self->bug($number);
|
|
$number = 1 if $number == 6;
|
|
next if $number == 5;
|
|
my $other_bug = $self->bug($number + 1);
|
|
Bugzilla->set_user($bug->reporter);
|
|
foreach my $field (@bug_id_fields) {
|
|
$bug->set_custom_field($field, $other_bug->id);
|
|
$bug->update($bug->delta_ts);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub _protect_bug_6 {
|
|
my ($self) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
Bugzilla->set_user($self->admin);
|
|
|
|
# Put bug6 in the nobody group.
|
|
my $nobody = $self->nobody;
|
|
# We pull it newly from the DB to be sure it's safe to call update()
|
|
# on.
|
|
my $bug6 = new Bugzilla::Bug($self->bug(6)->id);
|
|
$bug6->add_group($nobody);
|
|
$bug6->update($bug6->delta_ts);
|
|
|
|
# Remove the admin (and everybody else) from the $nobody group.
|
|
$dbh->do('DELETE FROM group_group_map
|
|
WHERE grantor_id = ? OR member_id = ?', undef,
|
|
$nobody->id, $nobody->id);
|
|
}
|
|
|
|
1;
|