diff options
Diffstat (limited to 'xt/lib/Bugzilla/Test')
-rw-r--r-- | xt/lib/Bugzilla/Test/Search.pm | 1495 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/AndTest.pm | 30 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/Constants.pm | 1834 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/CustomTest.pm | 81 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/FieldTest.pm | 832 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm | 129 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/InjectionTest.pm | 80 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/NotTest.pm | 35 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/OperatorTest.pm | 103 | ||||
-rw-r--r-- | xt/lib/Bugzilla/Test/Search/OrTest.pm | 139 |
10 files changed, 2436 insertions, 2322 deletions
diff --git a/xt/lib/Bugzilla/Test/Search.pm b/xt/lib/Bugzilla/Test/Search.pm index ca3bba5cf..31ba8bea3 100644 --- a/xt/lib/Bugzilla/Test/Search.pm +++ b/xt/lib/Bugzilla/Test/Search.pm @@ -59,8 +59,8 @@ use Scalar::Util qw(blessed); ############### sub new { - my ($class, $options) = @_; - return bless { options => $options }, $class; + my ($class, $options) = @_; + return bless {options => $options}, $class; } ############# @@ -68,198 +68,210 @@ sub new { ############# sub options { return $_[0]->{options} } -sub option { return $_[0]->{options}->{$_[1]} } +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; + 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; - + 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; + my ($self) = @_; + if (not $self->{all_operators}) { + + my @operators; + if (my $limit_operators = $self->option('operators')) { + @operators = split(',', $limit_operators); } - return @{ $self->{all_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} }; + 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; + 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; } - return @{ $self->{top_level_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; + 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} }; + 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]; + 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); + 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}; + 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}; + 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}; + 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}; + 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}; + 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}; + 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}; + 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}; } ############################## @@ -267,8 +279,8 @@ sub _bug_update_values { ############################## sub random { - $_[0] ||= FIELD_SIZE; - generate_random_password(@_); + $_[0] ||= FIELD_SIZE; + generate_random_password(@_); } # We need to use a custom timestamp for each create() and update(), @@ -276,53 +288,52 @@ sub random { # 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', - ); + 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" }); + 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; + 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 }); + 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 }); + 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}); } ######################### @@ -330,22 +341,22 @@ sub create_legal_value { ######################### 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, - }); - } + 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, + }); + } } ######################## @@ -353,221 +364,232 @@ sub _create_custom_fields { ######################## 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"; + 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{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()); + $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]; } - - # 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; - } + 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(); + $product->update(); - return \%values; + return \%values; } # Flags sub _create_flags { - my ($number, $setter, $requestee) = @_; + my ($number, $setter, $requestee) = @_; - my $flagtypes = _create_flagtypes($number); + my $flagtypes = _create_flagtypes($number); - my %flags; - foreach my $type (qw(a b)) { - $flags{$type} = _get_flag_values(@_, $flagtypes->{$type}); - } - return \%flags; + 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 + 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; + 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; + 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); } - else { - @set_flags = ({ type_id => $flagtype->id, status => '+', - setter => $setter, flagtype => $flagtype }); - } - return \@set_flags; + $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; + 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; } ################ @@ -575,194 +597,205 @@ sub _get_attach_values { ################ 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; + 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)); } - $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} = []; + + 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}; + } } - 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); + + 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}); } - - # Values for changedfrom. - $extra_values->{creation_ts} = $bug->creation_ts; - $extra_values->{delta_ts} = $bug->creation_ts; - - return new Bugzilla::Bug($bug->id); + $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); } ################################### @@ -778,44 +811,45 @@ sub _create_one_bug { # 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; + 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}; + 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; + 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; } ########## @@ -828,13 +862,13 @@ sub repopulate_test_results { # 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}; + 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 @@ -842,13 +876,13 @@ sub value_translation_cache { # 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}; + 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}; } ############# @@ -856,132 +890,135 @@ sub was_equal_cache { ############# 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->{'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(); + 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->{'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(); + 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); - } + 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); - } + 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); + 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; diff --git a/xt/lib/Bugzilla/Test/Search/AndTest.pm b/xt/lib/Bugzilla/Test/Search/AndTest.pm index f34ba1f3a..6132c0eb6 100644 --- a/xt/lib/Bugzilla/Test/Search/AndTest.pm +++ b/xt/lib/Bugzilla/Test/Search/AndTest.pm @@ -22,13 +22,13 @@ use constant type => 'AND'; # In an AND test, bugs ARE supposed to be contained only if they are contained # by ALL tests. sub bug_is_contained { - my ($self, $number) = @_; - return all { $_->bug_is_contained($number) } $self->field_tests; + my ($self, $number) = @_; + return all { $_->bug_is_contained($number) } $self->field_tests; } sub _bug_will_actually_be_contained { - my ($self, $number) = @_; - return all { $_->will_actually_contain_bug($number) } $self->field_tests; + my ($self, $number) = @_; + return all { $_->will_actually_contain_bug($number) } $self->field_tests; } ############################## @@ -36,17 +36,17 @@ sub _bug_will_actually_be_contained { ############################## sub search_params { - my ($self) = @_; - my @all_params = map { $_->search_params } $self->field_tests; - my %params; - my $chart = 0; - foreach my $item (@all_params) { - $params{"field0-$chart-0"} = $item->{'field0-0-0'}; - $params{"type0-$chart-0"} = $item->{'type0-0-0'}; - $params{"value0-$chart-0"} = $item->{'value0-0-0'}; - $chart++; - } - return \%params; + my ($self) = @_; + my @all_params = map { $_->search_params } $self->field_tests; + my %params; + my $chart = 0; + foreach my $item (@all_params) { + $params{"field0-$chart-0"} = $item->{'field0-0-0'}; + $params{"type0-$chart-0"} = $item->{'type0-0-0'}; + $params{"value0-$chart-0"} = $item->{'value0-0-0'}; + $chart++; + } + return \%params; } 1; diff --git a/xt/lib/Bugzilla/Test/Search/Constants.pm b/xt/lib/Bugzilla/Test/Search/Constants.pm index 5d84ec6ff..84080cfe8 100644 --- a/xt/lib/Bugzilla/Test/Search/Constants.pm +++ b/xt/lib/Bugzilla/Test/Search/Constants.pm @@ -17,28 +17,28 @@ use Bugzilla::Constants; use Bugzilla::Util qw(generate_random_password); our @EXPORT = qw( - ATTACHMENT_FIELDS - BROKEN_NOT - COLUMN_TRANSLATION - COMMENT_FIELDS - CUSTOM_FIELDS - CUSTOM_SEARCH_TESTS - FIELD_SIZE - FIELD_SUBSTR_SIZE - FLAG_FIELDS - INJECTION_BROKEN_FIELD - INJECTION_BROKEN_OPERATOR - INJECTION_TESTS - KNOWN_BROKEN - NUM_BUGS - NUM_SEARCH_TESTS - SKIP_FIELDS - SPECIAL_PARAM_TESTS - SUBSTR_NO_FIELD_ADD - SUBSTR_SIZE - TESTS - TESTS_PER_RUN - USER_FIELDS + ATTACHMENT_FIELDS + BROKEN_NOT + COLUMN_TRANSLATION + COMMENT_FIELDS + CUSTOM_FIELDS + CUSTOM_SEARCH_TESTS + FIELD_SIZE + FIELD_SUBSTR_SIZE + FLAG_FIELDS + INJECTION_BROKEN_FIELD + INJECTION_BROKEN_OPERATOR + INJECTION_TESTS + KNOWN_BROKEN + NUM_BUGS + NUM_SEARCH_TESTS + SKIP_FIELDS + SPECIAL_PARAM_TESTS + SUBSTR_NO_FIELD_ADD + SUBSTR_SIZE + TESTS + TESTS_PER_RUN + USER_FIELDS ); # Bug 1 is designed to be found by all the "equals" tests. It has @@ -63,6 +63,7 @@ use constant NUM_BUGS => 6; # How many tests there are for each operator/field combination other # than the "contains" tests. use constant NUM_SEARCH_TESTS => 3; + # This is how many tests get run for each field/operator. use constant TESTS_PER_RUN => NUM_SEARCH_TESTS + NUM_BUGS; @@ -74,40 +75,37 @@ use constant FIELD_SIZE => 30; # These are the custom fields that are created if the BZ_MODIFY_DATABASE_TESTS # environment variable is set. use constant CUSTOM_FIELDS => { - FIELD_TYPE_FREETEXT, 'cf_freetext', - FIELD_TYPE_SINGLE_SELECT, 'cf_single_select', - FIELD_TYPE_MULTI_SELECT, 'cf_multi_select', - FIELD_TYPE_TEXTAREA, 'cf_textarea', - FIELD_TYPE_DATETIME, 'cf_datetime', - FIELD_TYPE_BUG_ID, 'cf_bugid', + FIELD_TYPE_FREETEXT, 'cf_freetext', + FIELD_TYPE_SINGLE_SELECT, 'cf_single_select', + FIELD_TYPE_MULTI_SELECT, 'cf_multi_select', + FIELD_TYPE_TEXTAREA, 'cf_textarea', + FIELD_TYPE_DATETIME, 'cf_datetime', + FIELD_TYPE_BUG_ID, 'cf_bugid', }; # This translates fielddefs names into Search column names. use constant COLUMN_TRANSLATION => { - creation_ts => 'opendate', - delta_ts => 'changeddate', - work_time => 'actual_time', + creation_ts => 'opendate', + delta_ts => 'changeddate', + work_time => 'actual_time', }; # Make comment field names to their Bugzilla::Comment accessor. use constant COMMENT_FIELDS => { - longdesc => 'body', - commenter => 'author', - 'longdescs.isprivate' => 'is_private', + longdesc => 'body', + commenter => 'author', + 'longdescs.isprivate' => 'is_private', }; # Same as above, for Bugzilla::Attachment. -use constant ATTACHMENT_FIELDS => { - mimetype => 'contenttype', - submitter => 'attacher', - thedata => 'data', -}; +use constant ATTACHMENT_FIELDS => + {mimetype => 'contenttype', submitter => 'attacher', thedata => 'data',}; # Same, for Bugzilla::Flag. use constant FLAG_FIELDS => { - 'flagtypes.name' => 'name', - 'setters.login_name' => 'setter', - 'requestees.login_name' => 'requestee', + 'flagtypes.name' => 'name', + 'setters.login_name' => 'setter', + 'requestees.login_name' => 'requestee', }; # These are fields that we don't test. Test::More will mark these @@ -115,40 +113,43 @@ use constant FLAG_FIELDS => { # # We don't support days_elapsed or owner_idle_time yet. use constant SKIP_FIELDS => qw( - owner_idle_time - days_elapsed + owner_idle_time + days_elapsed ); # All the fields that represent users. use constant USER_FIELDS => qw( - assigned_to - cc - reporter - qa_contact - commenter - attachments.submitter - setters.login_name - requestees.login_name + assigned_to + cc + reporter + qa_contact + commenter + attachments.submitter + setters.login_name + requestees.login_name ); # For the "substr"-type searches, how short of a substring should # we use? The goal is to be shorter than the full string, but # long enough to still be globally unique. use constant SUBSTR_SIZE => 20; + # However, for some fields, we use a different size. use constant FIELD_SUBSTR_SIZE => { - alias => 11, - # Just the month and day. - deadline => -5, - creation_ts => -8, - delta_ts => -8, - percentage_complete => 1, - work_time => 3, - remaining_time => 3, - target_milestone => 15, - longdesc => 25, - # Just the hour and minute. - FIELD_TYPE_DATETIME, -5, + alias => 11, + + # Just the month and day. + deadline => -5, + creation_ts => -8, + delta_ts => -8, + percentage_complete => 1, + work_time => 3, + remaining_time => 3, + target_milestone => 15, + longdesc => 25, + + # Just the hour and minute. + FIELD_TYPE_DATETIME, -5, }; # For most fields, we add the length of the name of the field plus @@ -156,9 +157,9 @@ use constant FIELD_SUBSTR_SIZE => { # we're going to use. However, for some fields, it doesn't make sense to # add in their field name this way. use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw( - target_milestone remaining_time percentage_complete work_time - attachments.mimetype attachments.submitter attachments.filename - attachments.description flagtypes.name + target_milestone remaining_time percentage_complete work_time + attachments.mimetype attachments.submitter attachments.filename + attachments.description flagtypes.name ); ################ @@ -178,42 +179,42 @@ use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw( # lessthaneq. What we're really saying here by marking these broken # is that there ought to be some way of searching "all ccs" vs "any cc" # (and same for the other fields). -use constant GREATERTHAN_BROKEN => ( - cc => { contains => [1] }, -); +use constant GREATERTHAN_BROKEN => (cc => {contains => [1]},); # allwords and allwordssubstr have these broken tests in common. use constant ALLWORDS_BROKEN => ( - # allwordssubstr on cc fields matches against a single cc, - # instead of matching against all ccs on a bug. - cc => { contains => [1] }, - # bug 828344 changed how these searches operate to revert back to the 4.0 - # behavour, so these tests need to be updated (bug 849117). - 'flagtypes.name' => { contains => [1] }, - longdesc => { contains => [1] }, + + # allwordssubstr on cc fields matches against a single cc, + # instead of matching against all ccs on a bug. + cc => {contains => [1]}, + + # bug 828344 changed how these searches operate to revert back to the 4.0 + # behavour, so these tests need to be updated (bug 849117). + 'flagtypes.name' => {contains => [1]}, + longdesc => {contains => [1]}, ); # Fields that don't generally work at all with changed* searches, but # probably should. use constant CHANGED_BROKEN => ( - classification => { contains => [1] }, - commenter => { contains => [1] }, - percentage_complete => { contains => [1] }, - 'requestees.login_name' => { contains => [1] }, - 'setters.login_name' => { contains => [1] }, - delta_ts => { contains => [1] }, + classification => {contains => [1]}, + commenter => {contains => [1]}, + percentage_complete => {contains => [1]}, + 'requestees.login_name' => {contains => [1]}, + 'setters.login_name' => {contains => [1]}, + delta_ts => {contains => [1]}, ); # These are additional broken tests that changedfrom and changedto # have in common. use constant CHANGED_VALUE_BROKEN => ( - bug_group => { contains => [1] }, - cc => { contains => [1] }, - estimated_time => { contains => [1] }, - 'flagtypes.name' => { contains => [1] }, - keywords => { contains => [1] }, - 'longdescs.count' => { search => 1 }, - FIELD_TYPE_MULTI_SELECT, { contains => [1] }, + bug_group => {contains => [1]}, + cc => {contains => [1]}, + estimated_time => {contains => [1]}, + 'flagtypes.name' => {contains => [1]}, + keywords => {contains => [1]}, + 'longdescs.count' => {search => 1}, + FIELD_TYPE_MULTI_SELECT, {contains => [1]}, ); @@ -238,110 +239,92 @@ use constant CHANGED_VALUE_BROKEN => ( # while the other fails. In this case, we have a special override for # "operator-value", which uniquely identifies tests. use constant KNOWN_BROKEN => { - greaterthan => { GREATERTHAN_BROKEN }, - greaterthaneq => { GREATERTHAN_BROKEN }, + greaterthan => {GREATERTHAN_BROKEN}, + greaterthaneq => {GREATERTHAN_BROKEN}, - 'allwordssubstr-<1>' => { ALLWORDS_BROKEN }, - 'allwords-<1>' => { - ALLWORDS_BROKEN, - }, - 'anywords-<1>' => { - 'flagtypes.name' => { contains => [1,2,3,4,5] }, - }, - 'anywords-<1> <2>' => { - 'flagtypes.name' => { contains => [3,4,5] }, - }, - 'anywordssubstr-<1> <2>' => { - 'flagtypes.name' => { contains => [3,4,5] }, - }, + 'allwordssubstr-<1>' => {ALLWORDS_BROKEN}, + 'allwords-<1>' => {ALLWORDS_BROKEN,}, + 'anywords-<1>' => {'flagtypes.name' => {contains => [1, 2, 3, 4, 5]},}, + 'anywords-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},}, + 'anywordssubstr-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},}, - # setters.login_name and requestees.login name aren't tracked individually - # in bugs_activity, so can't be searched using this method. - # - # percentage_complete isn't tracked in bugs_activity (and it would be - # really hard to track). However, it adds a 0=0 term instead of using - # the changed* charts or simply denying them. - # - # delta_ts changedbefore/after should probably search for bugs based - # on their delta_ts. - # - # creation_ts changedbefore/after should search for bug creation dates. - # - # The commenter field changedbefore/after should search for comment - # creation dates. - # - # classification isn't being tracked properly in bugs_activity, I think. - # - # attach_data.thedata should search when attachments were created and - # who they were created by. - 'changedbefore' => { - CHANGED_BROKEN, - 'attach_data.thedata' => { contains => [1] }, - }, - 'changedafter' => { - 'attach_data.thedata' => { contains => [2,3,4] }, - classification => { contains => [2,3,4] }, - commenter => { contains => [2,3,4] }, - delta_ts => { contains => [2,3,4] }, - percentage_complete => { contains => [2,3,4] }, - 'requestees.login_name' => { contains => [2,3,4] }, - 'setters.login_name' => { contains => [2,3,4] }, - }, - changedfrom => { - CHANGED_BROKEN, - CHANGED_VALUE_BROKEN, - # All fields should have a way to search for "changing - # from a blank value" probably. - blocked => { contains => [3,4,5], no_criteria => 1 }, - dependson => { contains => [2,4,5], no_criteria => 1 }, - work_time => { contains => [1] }, - FIELD_TYPE_BUG_ID, { contains => [5], no_criteria => 1 }, - }, - # changeto doesn't find remaining_time changes (possibly due to us not - # tracking that data properly). - # - # multi-valued fields are stored as comma-separated strings, so you - # can't do changedfrom/to on them. - # - # Perhaps commenter can either tell you who the last commenter was, - # or if somebody commented at a given time (combined with other - # charts). - # - # longdesc changedto/from doesn't do anything; maybe it should. - # Same for attach_data.thedata. - changedto => { - CHANGED_BROKEN, - CHANGED_VALUE_BROKEN, - 'attach_data.thedata' => { contains => [1] }, - longdesc => { contains => [1] }, - remaining_time => { contains => [1] }, - }, - changedby => { - CHANGED_BROKEN, - # This should probably search the attacher or anybody who changed - # anything about an attachment at all. - 'attach_data.thedata' => { contains => [1] }, - # This should probably search the reporter. - creation_ts => { contains => [1] }, - }, - notequals => { - 'flagtypes.name' => { contains => [1, 5] }, - longdesc => { contains => [1] }, - }, - notregexp => { - 'flagtypes.name' => { contains => [1, 5] }, - longdesc => { contains => [1] }, - }, - notsubstring => { - 'flagtypes.name' => { contains => [5] }, - longdesc => { contains => [1] }, - }, - nowords => { - 'flagtypes.name' => { contains => [1, 5] }, - }, - nowordssubstr => { - 'flagtypes.name' => { contains => [5] }, - }, + # setters.login_name and requestees.login name aren't tracked individually + # in bugs_activity, so can't be searched using this method. + # + # percentage_complete isn't tracked in bugs_activity (and it would be + # really hard to track). However, it adds a 0=0 term instead of using + # the changed* charts or simply denying them. + # + # delta_ts changedbefore/after should probably search for bugs based + # on their delta_ts. + # + # creation_ts changedbefore/after should search for bug creation dates. + # + # The commenter field changedbefore/after should search for comment + # creation dates. + # + # classification isn't being tracked properly in bugs_activity, I think. + # + # attach_data.thedata should search when attachments were created and + # who they were created by. + 'changedbefore' => + {CHANGED_BROKEN, 'attach_data.thedata' => {contains => [1]},}, + 'changedafter' => { + 'attach_data.thedata' => {contains => [2, 3, 4]}, + classification => {contains => [2, 3, 4]}, + commenter => {contains => [2, 3, 4]}, + delta_ts => {contains => [2, 3, 4]}, + percentage_complete => {contains => [2, 3, 4]}, + 'requestees.login_name' => {contains => [2, 3, 4]}, + 'setters.login_name' => {contains => [2, 3, 4]}, + }, + changedfrom => { + CHANGED_BROKEN, CHANGED_VALUE_BROKEN, + + # All fields should have a way to search for "changing + # from a blank value" probably. + blocked => {contains => [3, 4, 5], no_criteria => 1}, + dependson => {contains => [2, 4, 5], no_criteria => 1}, + work_time => {contains => [1]}, + FIELD_TYPE_BUG_ID, {contains => [5], no_criteria => 1}, + }, + + # changeto doesn't find remaining_time changes (possibly due to us not + # tracking that data properly). + # + # multi-valued fields are stored as comma-separated strings, so you + # can't do changedfrom/to on them. + # + # Perhaps commenter can either tell you who the last commenter was, + # or if somebody commented at a given time (combined with other + # charts). + # + # longdesc changedto/from doesn't do anything; maybe it should. + # Same for attach_data.thedata. + changedto => { + CHANGED_BROKEN, CHANGED_VALUE_BROKEN, + 'attach_data.thedata' => {contains => [1]}, + longdesc => {contains => [1]}, + remaining_time => {contains => [1]}, + }, + changedby => { + CHANGED_BROKEN, + + # This should probably search the attacher or anybody who changed + # anything about an attachment at all. + 'attach_data.thedata' => {contains => [1]}, + + # This should probably search the reporter. + creation_ts => {contains => [1]}, + }, + notequals => + {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},}, + notregexp => + {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},}, + notsubstring => + {'flagtypes.name' => {contains => [5]}, longdesc => {contains => [1]},}, + nowords => {'flagtypes.name' => {contains => [1, 5]},}, + nowordssubstr => {'flagtypes.name' => {contains => [5]},}, }; ################### @@ -350,135 +333,90 @@ use constant KNOWN_BROKEN => { # Common BROKEN_NOT values for the changed* fields. use constant CHANGED_BROKEN_NOT => ( - "attach_data.thedata" => { contains => [1] }, - "classification" => { contains => [1] }, - "commenter" => { contains => [1] }, - "delta_ts" => { contains => [1] }, - percentage_complete => { contains => [1] }, - "requestees.login_name" => { contains => [1] }, - "setters.login_name" => { contains => [1] }, + "attach_data.thedata" => {contains => [1]}, + "classification" => {contains => [1]}, + "commenter" => {contains => [1]}, + "delta_ts" => {contains => [1]}, + percentage_complete => {contains => [1]}, + "requestees.login_name" => {contains => [1]}, + "setters.login_name" => {contains => [1]}, ); # For changedfrom and changedto. use constant CHANGED_FROM_TO_BROKEN_NOT => ( - 'longdescs.count' => { search => 1 }, - "bug_group" => { contains => [1] }, - "cc" => { contains => [1] }, - "estimated_time" => { contains => [1] }, - "flagtypes.name" => { contains => [1] }, - "keywords" => { contains => [1] }, - FIELD_TYPE_MULTI_SELECT, { contains => [1] }, + 'longdescs.count' => {search => 1}, + "bug_group" => {contains => [1]}, + "cc" => {contains => [1]}, + "estimated_time" => {contains => [1]}, + "flagtypes.name" => {contains => [1]}, + "keywords" => {contains => [1]}, + FIELD_TYPE_MULTI_SELECT, {contains => [1]}, ); # These are field/operator combinations that are broken when run under NOT(). use constant BROKEN_NOT => { - allwords => { - cc => { contains => [1] }, - 'flagtypes.name' => { contains => [1, 5] }, - longdesc => { contains => [1] }, - }, - 'allwords-<1> <2>' => { - cc => { }, - }, - allwordssubstr => { - cc => { contains => [1] }, - 'flagtypes.name' => { contains => [5, 6] }, - longdesc => { contains => [1] }, - }, - 'allwordssubstr-<1>,<2>' => { - cc => { }, - longdesc => { contains => [1] }, - }, - anyexact => { - 'flagtypes.name' => { contains => [1, 2, 5] }, - }, - 'anywords-<1>' => { - 'flagtypes.name' => { contains => [1, 2, 3, 4, 5] }, - }, - 'anywords-<1> <2>' => { - 'flagtypes.name' => { contains => [3, 4, 5] }, - }, - anywordssubstr => { - 'flagtypes.name' => { contains => [5] }, - }, - 'anywordssubstr-<1> <2>' => { - 'flagtypes.name' => { contains => [3,4,5] }, - }, - casesubstring => { - 'flagtypes.name' => { contains => [5] }, - }, - changedafter => { - "attach_data.thedata" => { contains => [2, 3, 4] }, - "classification" => { contains => [2, 3, 4] }, - "commenter" => { contains => [2, 3, 4] }, - percentage_complete => { contains => [2, 3, 4] }, - "delta_ts" => { contains => [2, 3, 4] }, - "requestees.login_name" => { contains => [2, 3, 4] }, - "setters.login_name" => { contains => [2, 3, 4] }, - }, - changedbefore => { - CHANGED_BROKEN_NOT, - }, - changedby => { - CHANGED_BROKEN_NOT, - creation_ts => { contains => [1] }, - work_time => { contains => [1] }, - }, - changedfrom => { - CHANGED_BROKEN_NOT, - CHANGED_FROM_TO_BROKEN_NOT, - 'attach_data.thedata' => { }, - blocked => { contains => [1, 2] }, - dependson => { contains => [1, 3] }, - work_time => { contains => [1] }, - FIELD_TYPE_BUG_ID, { contains => [1 .. 4] }, - }, - changedto => { - CHANGED_BROKEN_NOT, - CHANGED_FROM_TO_BROKEN_NOT, - longdesc => { contains => [1] }, - "remaining_time" => { contains => [1] }, - }, - greaterthan => { - cc => { contains => [1] }, - 'flagtypes.name' => { contains => [5] }, - }, - greaterthaneq => { - cc => { contains => [1] }, - 'flagtypes.name' => { contains => [2, 5] }, - }, - equals => { - 'flagtypes.name' => { contains => [1, 5] }, - }, - notequals => { - longdesc => { contains => [1] }, - }, - notregexp => { - longdesc => { contains => [1] }, - }, - notsubstring => { - longdesc => { contains => [1] }, - }, - 'nowords-<1>' => { - 'flagtypes.name' => { contains => [5] }, - }, - 'nowordssubstr-<1>' => { - 'flagtypes.name' => { contains => [5] }, - }, - lessthan => { - 'flagtypes.name' => { contains => [5] }, - }, - lessthaneq => { - 'flagtypes.name' => { contains => [1, 5] }, - }, - regexp => { - 'flagtypes.name' => { contains => [1, 5] }, - longdesc => { contains => [1] }, - }, - substring => { - 'flagtypes.name' => { contains => [5] }, - longdesc => { contains => [1] }, - }, + allwords => { + cc => {contains => [1]}, + 'flagtypes.name' => {contains => [1, 5]}, + longdesc => {contains => [1]}, + }, + 'allwords-<1> <2>' => {cc => {},}, + allwordssubstr => { + cc => {contains => [1]}, + 'flagtypes.name' => {contains => [5, 6]}, + longdesc => {contains => [1]}, + }, + 'allwordssubstr-<1>,<2>' => {cc => {}, longdesc => {contains => [1]},}, + anyexact => {'flagtypes.name' => {contains => [1, 2, 5]},}, + 'anywords-<1>' => {'flagtypes.name' => {contains => [1, 2, 3, 4, 5]},}, + 'anywords-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},}, + anywordssubstr => {'flagtypes.name' => {contains => [5]},}, + 'anywordssubstr-<1> <2>' => {'flagtypes.name' => {contains => [3, 4, 5]},}, + casesubstring => {'flagtypes.name' => {contains => [5]},}, + changedafter => { + "attach_data.thedata" => {contains => [2, 3, 4]}, + "classification" => {contains => [2, 3, 4]}, + "commenter" => {contains => [2, 3, 4]}, + percentage_complete => {contains => [2, 3, 4]}, + "delta_ts" => {contains => [2, 3, 4]}, + "requestees.login_name" => {contains => [2, 3, 4]}, + "setters.login_name" => {contains => [2, 3, 4]}, + }, + changedbefore => {CHANGED_BROKEN_NOT,}, + changedby => { + CHANGED_BROKEN_NOT, + creation_ts => {contains => [1]}, + work_time => {contains => [1]}, + }, + changedfrom => { + CHANGED_BROKEN_NOT, CHANGED_FROM_TO_BROKEN_NOT, + 'attach_data.thedata' => {}, + blocked => {contains => [1, 2]}, + dependson => {contains => [1, 3]}, + work_time => {contains => [1]}, + FIELD_TYPE_BUG_ID, {contains => [1 .. 4]}, + }, + changedto => { + CHANGED_BROKEN_NOT, CHANGED_FROM_TO_BROKEN_NOT, + longdesc => {contains => [1]}, + "remaining_time" => {contains => [1]}, + }, + greaterthan => + {cc => {contains => [1]}, 'flagtypes.name' => {contains => [5]},}, + greaterthaneq => + {cc => {contains => [1]}, 'flagtypes.name' => {contains => [2, 5]},}, + equals => {'flagtypes.name' => {contains => [1, 5]},}, + notequals => {longdesc => {contains => [1]},}, + notregexp => {longdesc => {contains => [1]},}, + notsubstring => {longdesc => {contains => [1]},}, + 'nowords-<1>' => {'flagtypes.name' => {contains => [5]},}, + 'nowordssubstr-<1>' => {'flagtypes.name' => {contains => [5]},}, + lessthan => {'flagtypes.name' => {contains => [5]},}, + lessthaneq => {'flagtypes.name' => {contains => [1, 5]},}, + regexp => + {'flagtypes.name' => {contains => [1, 5]}, longdesc => {contains => [1]},}, + substring => + {'flagtypes.name' => {contains => [5]}, longdesc => {contains => [1]},}, }; ############# @@ -489,113 +427,117 @@ use constant BROKEN_NOT => { # Regex tests need unique test values for certain fields. use constant REGEX_OVERRIDE => { - 'attachments.mimetype' => { value => '^text/x-1-' }, - bug_file_loc => { value => '^http://1-' }, - see_also => { value => '^http://1-' }, - blocked => { value => '^<1>$' }, - dependson => { value => '^<1>$' }, - bug_id => { value => '^<1>$' }, - 'attachments.isobsolete' => { value => '^1'}, - 'attachments.ispatch' => { value => '^1'}, - 'attachments.isprivate' => { value => '^1' }, - cclist_accessible => { value => '^1' }, - reporter_accessible => { value => '^1' }, - everconfirmed => { value => '^1' }, - 'longdescs.count' => { value => '^3' }, - 'longdescs.isprivate' => { value => '^1' }, - creation_ts => { value => '^2037-01-01' }, - delta_ts => { value => '^2037-01-01' }, - deadline => { value => '^2037-02-01' }, - estimated_time => { value => '^1.0' }, - remaining_time => { value => '^9.0' }, - work_time => { value => '^1.0' }, - longdesc => { value => '^1-' }, - percentage_complete => { value => '^10' }, - FIELD_TYPE_BUG_ID, { value => '^<1>$' }, - FIELD_TYPE_DATETIME, { value => '^2037-03-01' } + 'attachments.mimetype' => {value => '^text/x-1-'}, + bug_file_loc => {value => '^http://1-'}, + see_also => {value => '^http://1-'}, + blocked => {value => '^<1>$'}, + dependson => {value => '^<1>$'}, + bug_id => {value => '^<1>$'}, + 'attachments.isobsolete' => {value => '^1'}, + 'attachments.ispatch' => {value => '^1'}, + 'attachments.isprivate' => {value => '^1'}, + cclist_accessible => {value => '^1'}, + reporter_accessible => {value => '^1'}, + everconfirmed => {value => '^1'}, + 'longdescs.count' => {value => '^3'}, + 'longdescs.isprivate' => {value => '^1'}, + creation_ts => {value => '^2037-01-01'}, + delta_ts => {value => '^2037-01-01'}, + deadline => {value => '^2037-02-01'}, + estimated_time => {value => '^1.0'}, + remaining_time => {value => '^9.0'}, + work_time => {value => '^1.0'}, + longdesc => {value => '^1-'}, + percentage_complete => {value => '^10'}, + FIELD_TYPE_BUG_ID, {value => '^<1>$'}, FIELD_TYPE_DATETIME, + {value => '^2037-03-01'} }; # Common overrides between lessthan and lessthaneq. use constant LESSTHAN_OVERRIDE => ( - alias => { contains => [1,5] }, - estimated_time => { contains => [1,5] }, - qa_contact => { contains => [1,5] }, - resolution => { contains => [1,5] }, - status_whiteboard => { contains => [1,5] }, - FIELD_TYPE_TEXTAREA, { contains => [1,5] }, - FIELD_TYPE_FREETEXT, { contains => [1,5] }, + alias => {contains => [1, 5]}, + estimated_time => {contains => [1, 5]}, + qa_contact => {contains => [1, 5]}, + resolution => {contains => [1, 5]}, + status_whiteboard => {contains => [1, 5]}, + FIELD_TYPE_TEXTAREA, {contains => [1, 5]}, FIELD_TYPE_FREETEXT, + {contains => [1, 5]}, ); # The mandatorily-set fields have values higher than <1>, # so bug 5 shows up. use constant GREATERTHAN_OVERRIDE => ( - classification => { contains => [2,3,4,5] }, - assigned_to => { contains => [2,3,4,5] }, - bug_id => { contains => [2,3,4,5] }, - bug_group => { contains => [1,2,3,4] }, - bug_severity => { contains => [2,3,4,5] }, - bug_status => { contains => [2,3,4,5] }, - component => { contains => [2,3,4,5] }, - commenter => { contains => [2,3,4,5] }, - # keywords matches if *any* keyword matches - keywords => { contains => [1,2,3,4] }, - longdesc => { contains => [1,2,3,4] }, - op_sys => { contains => [2,3,4,5] }, - priority => { contains => [2,3,4,5] }, - product => { contains => [2,3,4,5] }, - reporter => { contains => [2,3,4,5] }, - rep_platform => { contains => [2,3,4,5] }, - short_desc => { contains => [2,3,4,5] }, - version => { contains => [2,3,4,5] }, - tag => { contains => [1,2,3,4] }, - target_milestone => { contains => [2,3,4,5] }, - # Bug 2 is the only bug besides 1 that has a Requestee set. - 'requestees.login_name' => { contains => [2] }, - FIELD_TYPE_SINGLE_SELECT, { contains => [2,3,4,5] }, - # Override SINGLE_SELECT for resolution. - resolution => { contains => [2,3,4] }, - # MULTI_SELECTs match if *any* value matches - FIELD_TYPE_MULTI_SELECT, { contains => [1,2,3,4] }, + classification => {contains => [2, 3, 4, 5]}, + assigned_to => {contains => [2, 3, 4, 5]}, + bug_id => {contains => [2, 3, 4, 5]}, + bug_group => {contains => [1, 2, 3, 4]}, + bug_severity => {contains => [2, 3, 4, 5]}, + bug_status => {contains => [2, 3, 4, 5]}, + component => {contains => [2, 3, 4, 5]}, + commenter => {contains => [2, 3, 4, 5]}, + + # keywords matches if *any* keyword matches + keywords => {contains => [1, 2, 3, 4]}, + longdesc => {contains => [1, 2, 3, 4]}, + op_sys => {contains => [2, 3, 4, 5]}, + priority => {contains => [2, 3, 4, 5]}, + product => {contains => [2, 3, 4, 5]}, + reporter => {contains => [2, 3, 4, 5]}, + rep_platform => {contains => [2, 3, 4, 5]}, + short_desc => {contains => [2, 3, 4, 5]}, + version => {contains => [2, 3, 4, 5]}, + tag => {contains => [1, 2, 3, 4]}, + target_milestone => {contains => [2, 3, 4, 5]}, + + # Bug 2 is the only bug besides 1 that has a Requestee set. + 'requestees.login_name' => {contains => [2]}, + FIELD_TYPE_SINGLE_SELECT, {contains => [2, 3, 4, 5]}, + + # Override SINGLE_SELECT for resolution. + resolution => {contains => [2, 3, 4]}, + + # MULTI_SELECTs match if *any* value matches + FIELD_TYPE_MULTI_SELECT, {contains => [1, 2, 3, 4]}, ); # For all positive multi-value types. use constant MULTI_BOOLEAN_OVERRIDE => ( - 'attachments.ispatch' => { value => '1,1', contains => [1] }, - 'attachments.isobsolete' => { value => '1,1', contains => [1] }, - 'attachments.isprivate' => { value => '1,1', contains => [1] }, - cclist_accessible => { value => '1,1', contains => [1] }, - reporter_accessible => { value => '1,1', contains => [1] }, - 'longdescs.isprivate' => { value => '1,1', contains => [1] }, - everconfirmed => { value => '1,1', contains => [1] }, + 'attachments.ispatch' => {value => '1,1', contains => [1]}, + 'attachments.isobsolete' => {value => '1,1', contains => [1]}, + 'attachments.isprivate' => {value => '1,1', contains => [1]}, + cclist_accessible => {value => '1,1', contains => [1]}, + reporter_accessible => {value => '1,1', contains => [1]}, + 'longdescs.isprivate' => {value => '1,1', contains => [1]}, + everconfirmed => {value => '1,1', contains => [1]}, ); # Same as above, for negative multi-value types. use constant NEGATIVE_MULTI_BOOLEAN_OVERRIDE => ( - 'attachments.ispatch' => { value => '1,1', contains => [2,3,4,5] }, - 'attachments.isobsolete' => { value => '1,1', contains => [2,3,4,5] }, - 'attachments.isprivate' => { value => '1,1', contains => [2,3,4,5] }, - cclist_accessible => { value => '1,1', contains => [2,3,4,5] }, - reporter_accessible => { value => '1,1', contains => [2,3,4,5] }, - 'longdescs.isprivate' => { value => '1,1', contains => [2,3,4,5] }, - everconfirmed => { value => '1,1', contains => [2,3,4,5] }, + 'attachments.ispatch' => {value => '1,1', contains => [2, 3, 4, 5]}, + 'attachments.isobsolete' => {value => '1,1', contains => [2, 3, 4, 5]}, + 'attachments.isprivate' => {value => '1,1', contains => [2, 3, 4, 5]}, + cclist_accessible => {value => '1,1', contains => [2, 3, 4, 5]}, + reporter_accessible => {value => '1,1', contains => [2, 3, 4, 5]}, + 'longdescs.isprivate' => {value => '1,1', contains => [2, 3, 4, 5]}, + everconfirmed => {value => '1,1', contains => [2, 3, 4, 5]}, ); # For anyexact and anywordssubstr use constant ANY_OVERRIDE => ( - 'longdescs.count' => { contains => [1,2,3,4] }, - 'work_time' => { value => '1.0,2.0' }, - dependson => { value => '<1>,<3>', contains => [1,3] }, - MULTI_BOOLEAN_OVERRIDE, + 'longdescs.count' => {contains => [1, 2, 3, 4]}, + 'work_time' => {value => '1.0,2.0'}, + dependson => {value => '<1>,<3>', contains => [1, 3]}, + MULTI_BOOLEAN_OVERRIDE, ); # For all the changed* searches. The ones that have empty contains # are fields that never change in value, or will never be rationally # tracked in bugs_activity. use constant CHANGED_OVERRIDE => ( - 'attachments.submitter' => { contains => [] }, - bug_id => { contains => [] }, - reporter => { contains => [] }, - tag => { contains => [] }, + 'attachments.submitter' => {contains => []}, + bug_id => {contains => []}, + reporter => {contains => []}, + tag => {contains => []}, ); ######### @@ -637,7 +579,7 @@ use constant CHANGED_OVERRIDE => ( # on Bug 1. If we did an "anywordssubstr" search test, it would # become a space-separated string of the first few characters # of each CC's login name on Bug 1. -# +# # <#-id> - The bug id of the numbered bug. # <#-reporter> - The login name of the numbered bug's reporter. # <#-delta> - The delta_ts of the numbered bug. @@ -655,301 +597,330 @@ use constant CHANGED_OVERRIDE => ( # override: This allows you to override "contains" and "values" for # certain fields. use constant TESTS => { - equals => [ - { contains => [1], value => '<1>' }, - ], - notequals => [ - { contains => [2,3,4,5], value => '<1>' }, - ], - substring => [ - { contains => [1], value => '<1>', - override => { - percentage_complete => { contains => [1,2,3] }, - } - }, - ], - casesubstring => [ - { contains => [1], value => '<1>', - override => { - percentage_complete => { contains => [1,2,3] }, - } - }, - { contains => [], value => '<1>', transform => sub { lc($_[0]) }, - extra_name => 'lc', if_equal => { contains => [1] }, - override => { - percentage_complete => { contains => [1,2,3] }, - } - }, - ], - notsubstring => [ - { contains => [2,3,4,5], value => '<1>', - override => { - percentage_complete => { contains => [4,5] }, - }, - } - ], - regexp => [ - { contains => [1], value => '<1>', escape => 1, - override => { - percentage_complete => { value => '^10' }, - } - }, - { contains => [1], value => '^1-', override => REGEX_OVERRIDE }, - ], - notregexp => [ - { contains => [2,3,4,5], value => '<1>', escape => 1, - override => { - percentage_complete => { value => '^10' }, - } - }, - { contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE }, - ], - lessthan => [ - { contains => [1], value => 2, - override => { - # A lot of these contain bug 5 because an empty value is validly - # less than the specified value. - bug_file_loc => { value => 'http://2-', contains => [1,5] }, - see_also => { value => 'http://2-' }, - 'attachments.mimetype' => { value => 'text/x-2-' }, - blocked => { value => '<4-id>', contains => [1,2] }, - dependson => { value => '<3-id>', contains => [1,3] }, - bug_id => { value => '<2-id>' }, - 'attachments.isprivate' => { value => 1, contains => [2,3,4] }, - 'attachments.isobsolete' => { value => 1, contains => [2,3,4] }, - 'attachments.ispatch' => { value => 1, contains => [2,3,4] }, - cclist_accessible => { value => 1, contains => [2,3,4,5] }, - reporter_accessible => { value => 1, contains => [2,3,4,5] }, - 'longdescs.count' => { value => 3, contains => [2,3,4,5] }, - 'longdescs.isprivate' => { value => 1, contains => [1,2,3,4,5] }, - everconfirmed => { value => 1, contains => [2,3,4,5] }, - creation_ts => { value => '2037-01-02', contains => [1,5] }, - delta_ts => { value => '2037-01-02', contains => [1,5] }, - deadline => { value => '2037-02-02', contains => [1,5] }, - remaining_time => { value => 10, contains => [1,5] }, - percentage_complete => { value => 11, contains => [1,5] }, - longdesc => { value => '2-', contains => [1,5] }, - work_time => { value => 1, contains => [5] }, - FIELD_TYPE_BUG_ID, { value => '<2>', contains => [1,5] }, - FIELD_TYPE_DATETIME, { value => '2037-03-02', contains => [1,5] }, - LESSTHAN_OVERRIDE, - } - }, - ], - lessthaneq => [ - { contains => [1], value => '<1>', - override => { - 'attachments.isobsolete' => { value => 0, contains => [2,3,4] }, - 'attachments.ispatch' => { value => 0, contains => [2,3,4] }, - 'attachments.isprivate' => { value => 0, contains => [2,3,4] }, - cclist_accessible => { value => 0, contains => [2,3,4,5] }, - reporter_accessible => { value => 0, contains => [2,3,4,5] }, - 'longdescs.count' => { value => 2, contains => [2,3,4,5] }, - 'longdescs.isprivate' => { value => -1, contains => [] }, - everconfirmed => { value => 0, contains => [2,3,4,5] }, - bug_file_loc => { contains => [1,5] }, - blocked => { contains => [1,2] }, - deadline => { contains => [1,5] }, - dependson => { contains => [1,3] }, - creation_ts => { contains => [1,5] }, - delta_ts => { contains => [1,5] }, - remaining_time => { contains => [1,5] }, - longdesc => { contains => [1,5] }, - percentage_complete => { contains => [1,5] }, - work_time => { value => 1, contains => [1,5] }, - FIELD_TYPE_BUG_ID, { contains => [1,5] }, - FIELD_TYPE_DATETIME, { contains => [1,5] }, - LESSTHAN_OVERRIDE, - }, - }, - ], - greaterthan => [ - { contains => [2,3,4], value => '<1>', - override => { - dependson => { contains => [3] }, - blocked => { contains => [2] }, - 'attachments.ispatch' => { value => 0, contains => [1] }, - 'attachments.isobsolete' => { value => 0, contains => [1] }, - 'attachments.isprivate' => { value => 0, contains => [1] }, - cclist_accessible => { value => 0, contains => [1] }, - reporter_accessible => { value => 0, contains => [1] }, - 'longdescs.count' => { value => 2, contains => [1] }, - 'longdescs.isprivate' => { value => 0, contains => [1] }, - everconfirmed => { value => 0, contains => [1] }, - 'flagtypes.name' => { value => 2, contains => [2,3,4] }, - GREATERTHAN_OVERRIDE, - }, - }, - ], - greaterthaneq => [ - { contains => [2,3,4], value => '<2>', - override => { - 'attachments.ispatch' => { value => 1, contains => [1] }, - 'attachments.isobsolete' => { value => 1, contains => [1] }, - 'attachments.isprivate' => { value => 1, contains => [1] }, - cclist_accessible => { value => 1, contains => [1] }, - reporter_accessible => { value => 1, contains => [1] }, - 'longdescs.count' => { value => 3, contains => [1] }, - 'longdescs.isprivate' => { value => 1, contains => [1] }, - everconfirmed => { value => 1, contains => [1] }, - dependson => { value => '<3>', contains => [1,3] }, - blocked => { contains => [1,2] }, - GREATERTHAN_OVERRIDE, - } - }, - ], - matches => [ - { contains => [1], value => '<1>' }, - ], - notmatches => [ - { contains => [2,3,4,5], value => '<1>' }, - ], - anyexact => [ - { contains => [1,2], value => '<1>, <2>', - override => { ANY_OVERRIDE } }, - ], - anywordssubstr => [ - { contains => [1,2], value => '<1> <2>', - override => { - ANY_OVERRIDE, - percentage_complete => { contains => [1,2,3] }, - } - }, - ], - allwordssubstr => [ - { contains => [1], value => '<1>', - override => { - MULTI_BOOLEAN_OVERRIDE, - # We search just the number "1" for percentage_complete, - # which matches a lot of bugs. - percentage_complete => { contains => [1,2,3] }, - }, - }, - { contains => [], value => '<1>,<2>', - override => { - dependson => { value => '<1-id> <3-id>', contains => [] }, - # bug 3 has the value "21" here, so matches "2,1" - percentage_complete => { value => '<2>,<3>', contains => [3] }, - # 1 0 matches bug 1, which has both public and private comments. - 'longdescs.isprivate' => { contains => [1] }, - } - }, - ], - nowordssubstr => [ - { contains => [2,3,4,5], value => '<1>', - override => { - # longdescs.isprivate translates to "1 0", so no bugs should - # show up. - 'longdescs.isprivate' => { contains => [] }, - percentage_complete => { contains => [4,5] }, - work_time => { contains => [2,3,4,5] }, - } - }, - ], - anywords => [ - { contains => [1], value => '<1>', - override => { - MULTI_BOOLEAN_OVERRIDE, - } - }, - { contains => [1,2], value => '<1> <2>', - override => { - MULTI_BOOLEAN_OVERRIDE, - dependson => { value => '<1> <3>', contains => [1,3] }, - 'longdescs.count' => { contains => [1,2,3,4] }, - }, - }, - ], - allwords => [ - { contains => [1], value => '<1>', - override => { MULTI_BOOLEAN_OVERRIDE } }, - { contains => [], value => '<1> <2>', - override => { - dependson => { contains => [], value => '<2-id> <3-id>' }, - # 1 0 matches bug 1, which has both public and private comments. - 'longdescs.isprivate' => { contains => [1] }, - } - }, - ], - nowords => [ - { contains => [2,3,4,5], value => '<1>', - override => { - # longdescs.isprivate translates to "1 0", so no bugs should - # show up. - 'longdescs.isprivate' => { contains => [] }, - work_time => { contains => [2,3,4,5] }, - } - }, - ], - - changedbefore => [ - { contains => [1], value => '<1-delta>', - override => { - CHANGED_OVERRIDE, - creation_ts => { contains => [1,5] }, - blocked => { contains => [1,2] }, - dependson => { contains => [1,3] }, - longdesc => { contains => [1,5] }, - 'longdescs.count' => { contains => [1,5] }, - } - }, - ], - changedafter => [ - { contains => [2,3,4], value => '<2-delta>', - override => { - CHANGED_OVERRIDE, - creation_ts => { contains => [3,4] }, - # We only change this for one bug, and it doesn't match. - 'longdescs.isprivate' => { contains => [] }, - # Same for everconfirmed. - 'everconfirmed' => { contains => [] }, - # For blocked and dependson, they have the delta_ts of bug1 - # in the bugs_activity table, so they won't ever match. - blocked => { contains => [] }, - dependson => { contains => [] }, - } - }, - ], - changedfrom => [ - { contains => [1], value => '<1>', - override => { - CHANGED_OVERRIDE, - # The test never changes an already-set dependency field, but - # we *can* attempt to test searching against an empty value, - # which should get us some bugs. - blocked => { value => '', contains => [1,2] }, - dependson => { value => '', contains => [1,3] }, - FIELD_TYPE_BUG_ID, { value => '', contains => [1,2,3,4] }, - # longdesc changedfrom doesn't make any sense. - longdesc => { contains => [] }, - # Nor does creation_ts changedfrom. - creation_ts => { contains => [] }, - 'attach_data.thedata' => { contains => [] }, - bug_id => { value => '<1-id>', contains => [] }, - }, - }, - ], - changedto => [ - { contains => [1], value => '<1>', - override => { - CHANGED_OVERRIDE, - # I can't imagine any use for creation_ts changedto. - creation_ts => { contains => [] }, - } - }, - ], - changedby => [ - { contains => [1], value => '<1-reporter>', - override => { - CHANGED_OVERRIDE, - blocked => { contains => [1,2] }, - dependson => { contains => [1,3] }, - }, - }, - ], - # XXX these need tests developed - isempty => [], - isnotempty => [], + equals => [{contains => [1], value => '<1>'},], + notequals => [{contains => [2, 3, 4, 5], value => '<1>'},], + substring => [ + { + contains => [1], + value => '<1>', + override => {percentage_complete => {contains => [1, 2, 3]},} + }, + ], + casesubstring => [ + { + contains => [1], + value => '<1>', + override => {percentage_complete => {contains => [1, 2, 3]},} + }, + { + contains => [], + value => '<1>', + transform => sub { lc($_[0]) }, + extra_name => 'lc', + if_equal => {contains => [1]}, + override => {percentage_complete => {contains => [1, 2, 3]},} + }, + ], + notsubstring => [{ + contains => [2, 3, 4, 5], + value => '<1>', + override => {percentage_complete => {contains => [4, 5]},}, + }], + regexp => [ + { + contains => [1], + value => '<1>', + escape => 1, + override => {percentage_complete => {value => '^10'},} + }, + {contains => [1], value => '^1-', override => REGEX_OVERRIDE}, + ], + notregexp => [ + { + contains => [2, 3, 4, 5], + value => '<1>', + escape => 1, + override => {percentage_complete => {value => '^10'},} + }, + {contains => [2, 3, 4, 5], value => '^1-', override => REGEX_OVERRIDE}, + ], + lessthan => [ + { + contains => [1], + value => 2, + override => { + + # A lot of these contain bug 5 because an empty value is validly + # less than the specified value. + bug_file_loc => {value => 'http://2-', contains => [1, 5]}, + see_also => {value => 'http://2-'}, + 'attachments.mimetype' => {value => 'text/x-2-'}, + blocked => {value => '<4-id>', contains => [1, 2]}, + dependson => {value => '<3-id>', contains => [1, 3]}, + bug_id => {value => '<2-id>'}, + 'attachments.isprivate' => {value => 1, contains => [2, 3, 4]}, + 'attachments.isobsolete' => {value => 1, contains => [2, 3, 4]}, + 'attachments.ispatch' => {value => 1, contains => [2, 3, 4]}, + cclist_accessible => {value => 1, contains => [2, 3, 4, 5]}, + reporter_accessible => {value => 1, contains => [2, 3, 4, 5]}, + 'longdescs.count' => {value => 3, contains => [2, 3, 4, 5]}, + 'longdescs.isprivate' => {value => 1, contains => [1, 2, 3, 4, 5]}, + everconfirmed => {value => 1, contains => [2, 3, 4, 5]}, + creation_ts => {value => '2037-01-02', contains => [1, 5]}, + delta_ts => {value => '2037-01-02', contains => [1, 5]}, + deadline => {value => '2037-02-02', contains => [1, 5]}, + remaining_time => {value => 10, contains => [1, 5]}, + percentage_complete => {value => 11, contains => [1, 5]}, + longdesc => {value => '2-', contains => [1, 5]}, + work_time => {value => 1, contains => [5]}, + FIELD_TYPE_BUG_ID, {value => '<2>', contains => [1, 5]}, FIELD_TYPE_DATETIME, + {value => '2037-03-02', contains => [1, 5]}, LESSTHAN_OVERRIDE, + } + }, + ], + lessthaneq => [ + { + contains => [1], + value => '<1>', + override => { + 'attachments.isobsolete' => {value => 0, contains => [2, 3, 4]}, + 'attachments.ispatch' => {value => 0, contains => [2, 3, 4]}, + 'attachments.isprivate' => {value => 0, contains => [2, 3, 4]}, + cclist_accessible => {value => 0, contains => [2, 3, 4, 5]}, + reporter_accessible => {value => 0, contains => [2, 3, 4, 5]}, + 'longdescs.count' => {value => 2, contains => [2, 3, 4, 5]}, + 'longdescs.isprivate' => {value => -1, contains => []}, + everconfirmed => {value => 0, contains => [2, 3, 4, 5]}, + bug_file_loc => {contains => [1, 5]}, + blocked => {contains => [1, 2]}, + deadline => {contains => [1, 5]}, + dependson => {contains => [1, 3]}, + creation_ts => {contains => [1, 5]}, + delta_ts => {contains => [1, 5]}, + remaining_time => {contains => [1, 5]}, + longdesc => {contains => [1, 5]}, + percentage_complete => {contains => [1, 5]}, + work_time => {value => 1, contains => [1, 5]}, + FIELD_TYPE_BUG_ID, {contains => [1, 5]}, FIELD_TYPE_DATETIME, + {contains => [1, 5]}, LESSTHAN_OVERRIDE, + }, + }, + ], + greaterthan => [ + { + contains => [2, 3, 4], + value => '<1>', + override => { + dependson => {contains => [3]}, + blocked => {contains => [2]}, + 'attachments.ispatch' => {value => 0, contains => [1]}, + 'attachments.isobsolete' => {value => 0, contains => [1]}, + 'attachments.isprivate' => {value => 0, contains => [1]}, + cclist_accessible => {value => 0, contains => [1]}, + reporter_accessible => {value => 0, contains => [1]}, + 'longdescs.count' => {value => 2, contains => [1]}, + 'longdescs.isprivate' => {value => 0, contains => [1]}, + everconfirmed => {value => 0, contains => [1]}, + 'flagtypes.name' => {value => 2, contains => [2, 3, 4]}, + GREATERTHAN_OVERRIDE, + }, + }, + ], + greaterthaneq => [ + { + contains => [2, 3, 4], + value => '<2>', + override => { + 'attachments.ispatch' => {value => 1, contains => [1]}, + 'attachments.isobsolete' => {value => 1, contains => [1]}, + 'attachments.isprivate' => {value => 1, contains => [1]}, + cclist_accessible => {value => 1, contains => [1]}, + reporter_accessible => {value => 1, contains => [1]}, + 'longdescs.count' => {value => 3, contains => [1]}, + 'longdescs.isprivate' => {value => 1, contains => [1]}, + everconfirmed => {value => 1, contains => [1]}, + dependson => {value => '<3>', contains => [1, 3]}, + blocked => {contains => [1, 2]}, + GREATERTHAN_OVERRIDE, + } + }, + ], + matches => [{contains => [1], value => '<1>'},], + notmatches => [{contains => [2, 3, 4, 5], value => '<1>'},], + anyexact => + [{contains => [1, 2], value => '<1>, <2>', override => {ANY_OVERRIDE}},], + anywordssubstr => [ + { + contains => [1, 2], + value => '<1> <2>', + override => {ANY_OVERRIDE, percentage_complete => {contains => [1, 2, 3]},} + }, + ], + allwordssubstr => [ + { + contains => [1], + value => '<1>', + override => { + MULTI_BOOLEAN_OVERRIDE, + + # We search just the number "1" for percentage_complete, + # which matches a lot of bugs. + percentage_complete => {contains => [1, 2, 3]}, + }, + }, + { + contains => [], + value => '<1>,<2>', + override => { + dependson => {value => '<1-id> <3-id>', contains => []}, + + # bug 3 has the value "21" here, so matches "2,1" + percentage_complete => {value => '<2>,<3>', contains => [3]}, + + # 1 0 matches bug 1, which has both public and private comments. + 'longdescs.isprivate' => {contains => [1]}, + } + }, + ], + nowordssubstr => [ + { + contains => [2, 3, 4, 5], + value => '<1>', + override => { + + # longdescs.isprivate translates to "1 0", so no bugs should + # show up. + 'longdescs.isprivate' => {contains => []}, + percentage_complete => {contains => [4, 5]}, + work_time => {contains => [2, 3, 4, 5]}, + } + }, + ], + anywords => [ + {contains => [1], value => '<1>', override => {MULTI_BOOLEAN_OVERRIDE,}}, + { + contains => [1, 2], + value => '<1> <2>', + override => { + MULTI_BOOLEAN_OVERRIDE, + dependson => {value => '<1> <3>', contains => [1, 3]}, + 'longdescs.count' => {contains => [1, 2, 3, 4]}, + }, + }, + ], + allwords => [ + {contains => [1], value => '<1>', override => {MULTI_BOOLEAN_OVERRIDE}}, + { + contains => [], + value => '<1> <2>', + override => { + dependson => {contains => [], value => '<2-id> <3-id>'}, + + # 1 0 matches bug 1, which has both public and private comments. + 'longdescs.isprivate' => {contains => [1]}, + } + }, + ], + nowords => [ + { + contains => [2, 3, 4, 5], + value => '<1>', + override => { + + # longdescs.isprivate translates to "1 0", so no bugs should + # show up. + 'longdescs.isprivate' => {contains => []}, + work_time => {contains => [2, 3, 4, 5]}, + } + }, + ], + + changedbefore => [ + { + contains => [1], + value => '<1-delta>', + override => { + CHANGED_OVERRIDE, + creation_ts => {contains => [1, 5]}, + blocked => {contains => [1, 2]}, + dependson => {contains => [1, 3]}, + longdesc => {contains => [1, 5]}, + 'longdescs.count' => {contains => [1, 5]}, + } + }, + ], + changedafter => [ + { + contains => [2, 3, 4], + value => '<2-delta>', + override => { + CHANGED_OVERRIDE, + creation_ts => {contains => [3, 4]}, + + # We only change this for one bug, and it doesn't match. + 'longdescs.isprivate' => {contains => []}, + + # Same for everconfirmed. + 'everconfirmed' => {contains => []}, + + # For blocked and dependson, they have the delta_ts of bug1 + # in the bugs_activity table, so they won't ever match. + blocked => {contains => []}, + dependson => {contains => []}, + } + }, + ], + changedfrom => [ + { + contains => [1], + value => '<1>', + override => { + CHANGED_OVERRIDE, + + # The test never changes an already-set dependency field, but + # we *can* attempt to test searching against an empty value, + # which should get us some bugs. + blocked => {value => '', contains => [1, 2]}, + dependson => {value => '', contains => [1, 3]}, + FIELD_TYPE_BUG_ID, {value => '', contains => [1, 2, 3, 4]}, + + # longdesc changedfrom doesn't make any sense. + longdesc => {contains => []}, + + # Nor does creation_ts changedfrom. + creation_ts => {contains => []}, + 'attach_data.thedata' => {contains => []}, + bug_id => {value => '<1-id>', contains => []}, + }, + }, + ], + changedto => [ + { + contains => [1], + value => '<1>', + override => { + CHANGED_OVERRIDE, + + # I can't imagine any use for creation_ts changedto. + creation_ts => {contains => []}, + } + }, + ], + changedby => [ + { + contains => [1], + value => '<1-reporter>', + override => { + CHANGED_OVERRIDE, + blocked => {contains => [1, 2]}, + dependson => {contains => [1, 3]}, + }, + }, + ], + + # XXX these need tests developed + isempty => [], + isnotempty => [], }; # Fields that do not behave as we expect, for InjectionTest. @@ -958,59 +929,62 @@ use constant TESTS => { # operator_ok overrides the "brokenness" of certain operators, so that they # are always OK for that field/operator combination. use constant INJECTION_BROKEN_FIELD => { - # Pg can't run injection tests against integer or date fields. See bug 577557. - 'attachments.isobsolete' => { db_skip => ['Pg'] }, - 'attachments.ispatch' => { db_skip => ['Pg'] }, - 'attachments.isprivate' => { db_skip => ['Pg'] }, - blocked => { db_skip => ['Pg'] }, - bug_id => { db_skip => ['Pg'] }, - cclist_accessible => { db_skip => ['Pg'] }, - creation_ts => { db_skip => ['Pg'] }, - days_elapsed => { db_skip => ['Pg'] }, - dependson => { db_skip => ['Pg'] }, - deadline => { db_skip => ['Pg'] }, - delta_ts => { db_skip => ['Pg'] }, - estimated_time => { db_skip => ['Pg'] }, - everconfirmed => { db_skip => ['Pg'] }, - 'longdescs.isprivate' => { db_skip => ['Pg'] }, - percentage_complete => { db_skip => ['Pg'] }, - remaining_time => { db_skip => ['Pg'] }, - reporter_accessible => { db_skip => ['Pg'] }, - work_time => { db_skip => ['Pg'] }, - FIELD_TYPE_BUG_ID, { db_skip => ['Pg'] }, - FIELD_TYPE_DATETIME, { db_skip => ['Pg'] }, - owner_idle_time => { search => 1 }, - 'longdescs.count' => { - search => 1, - db_skip => ['Pg'], - operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring - changedbefore changedafter greaterthan greaterthaneq - lessthan lessthaneq notregexp notsubstring - nowordssubstr regexp substring anywords - notequals nowords equals anyexact)], - }, + + # Pg can't run injection tests against integer or date fields. See bug 577557. + 'attachments.isobsolete' => {db_skip => ['Pg']}, + 'attachments.ispatch' => {db_skip => ['Pg']}, + 'attachments.isprivate' => {db_skip => ['Pg']}, + blocked => {db_skip => ['Pg']}, + bug_id => {db_skip => ['Pg']}, + cclist_accessible => {db_skip => ['Pg']}, + creation_ts => {db_skip => ['Pg']}, + days_elapsed => {db_skip => ['Pg']}, + dependson => {db_skip => ['Pg']}, + deadline => {db_skip => ['Pg']}, + delta_ts => {db_skip => ['Pg']}, + estimated_time => {db_skip => ['Pg']}, + everconfirmed => {db_skip => ['Pg']}, + 'longdescs.isprivate' => {db_skip => ['Pg']}, + percentage_complete => {db_skip => ['Pg']}, + remaining_time => {db_skip => ['Pg']}, + reporter_accessible => {db_skip => ['Pg']}, + work_time => {db_skip => ['Pg']}, + FIELD_TYPE_BUG_ID, + {db_skip => ['Pg']}, + FIELD_TYPE_DATETIME, + {db_skip => ['Pg']}, + owner_idle_time => {search => 1}, + 'longdescs.count' => { + search => 1, + db_skip => ['Pg'], + operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring + changedbefore changedafter greaterthan greaterthaneq + lessthan lessthaneq notregexp notsubstring + nowordssubstr regexp substring anywords + notequals nowords equals anyexact)], + }, }; # Operators that do not behave as we expect, for InjectionTest. # search => 1 means the Bugzilla::Search creation fails, but # field_ok contains fields that it does actually succeed for. use constant INJECTION_BROKEN_OPERATOR => { - changedafter => { search => 1, field_ok => ['creation_ts'] }, - changedbefore => { search => 1, field_ok => ['creation_ts'] }, - changedby => { search => 1 }, - isempty => { search => 1 }, - isnotempty => { search => 1 }, + changedafter => {search => 1, field_ok => ['creation_ts']}, + changedbefore => {search => 1, field_ok => ['creation_ts']}, + changedby => {search => 1}, + isempty => {search => 1}, + isnotempty => {search => 1}, }; # Tests run by Bugzilla::Test::Search::InjectionTest. # We have to make sure the values are all one word or they'll be split # up by the multi-word tests. use constant INJECTION_TESTS => ( - { value => ';SEMICOLON_TEST' }, - { value => '--COMMENT_TEST' }, - { value => "'QUOTE_TEST" }, - { value => "';QUOTE_SEMICOLON_TEST" }, - { value => '/*STAR_COMMENT_TEST' } + {value => ';SEMICOLON_TEST'}, + {value => '--COMMENT_TEST'}, + {value => "'QUOTE_TEST"}, + {value => "';QUOTE_SEMICOLON_TEST"}, + {value => '/*STAR_COMMENT_TEST'} ); ################# @@ -1018,185 +992,257 @@ use constant INJECTION_TESTS => ( ################# use constant SPECIAL_PARAM_TESTS => ( - { field => 'bug_status', operator => 'anyexact', value => '__open__', - contains => [5] }, - { field => 'bug_status', operator => 'anyexact', value => '__closed__', - contains => [1,2,3,4] }, - { field => 'bug_status', operator => 'anyexact', value => '__all__', - contains => [1,2,3,4,5] }, - - { field => 'resolution', operator => 'anyexact', value => '---', - contains => [5] }, - - # email* query parameters. - { field => 'assigned_to', operator => 'anyexact', - value => '<1>, <2-reporter>', contains => [1,2], - extra_params => { emailreporter1 => 1 } }, - { field => 'assigned_to', operator => 'equals', - value => '<1>', extra_name => 'email2', contains => [], - extra_params => { - email2 => generate_random_password(100), emaillongdesc2 => 1, - }, - }, - - # standard pronouns - { field => 'assigned_to', operator => 'equals', value => '%assignee%', - contains => [1,2,3,4,5] }, - { field => 'reporter', operator => 'equals', value => '%reporter%', - contains => [1,2,3,4,5] }, - { field => 'qa_contact', operator => 'equals', value => '%qacontact%', - contains => [1,2,3,4,5] }, - { field => 'cc', operator => 'equals', value => '%user%', - contains => [1] }, - # group pronouns - { field => 'reporter', operator => 'equals', - value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] }, - { field => 'assigned_to', operator => 'equals', - value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] }, - { field => 'qa_contact', operator => 'equals', - value => '%group.<1-bug_group>%', contains => [1,2,3,4] }, - { field => 'cc', operator => 'equals', - value => '%group.<1-bug_group>%', contains => [1,2,3,4] }, - { field => 'commenter', operator => 'equals', - value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] }, + { + field => 'bug_status', + operator => 'anyexact', + value => '__open__', + contains => [5] + }, + { + field => 'bug_status', + operator => 'anyexact', + value => '__closed__', + contains => [1, 2, 3, 4] + }, + { + field => 'bug_status', + operator => 'anyexact', + value => '__all__', + contains => [1, 2, 3, 4, 5] + }, + + { + field => 'resolution', + operator => 'anyexact', + value => '---', + contains => [5] + }, + + # email* query parameters. + { + field => 'assigned_to', + operator => 'anyexact', + value => '<1>, <2-reporter>', + contains => [1, 2], + extra_params => {emailreporter1 => 1} + }, + { + field => 'assigned_to', + operator => 'equals', + value => '<1>', + extra_name => 'email2', + contains => [], + extra_params => {email2 => generate_random_password(100), emaillongdesc2 => 1,}, + }, + + # standard pronouns + { + field => 'assigned_to', + operator => 'equals', + value => '%assignee%', + contains => [1, 2, 3, 4, 5] + }, + { + field => 'reporter', + operator => 'equals', + value => '%reporter%', + contains => [1, 2, 3, 4, 5] + }, + { + field => 'qa_contact', + operator => 'equals', + value => '%qacontact%', + contains => [1, 2, 3, 4, 5] + }, + {field => 'cc', operator => 'equals', value => '%user%', contains => [1]}, + + # group pronouns + { + field => 'reporter', + operator => 'equals', + value => '%group.<1-bug_group>%', + contains => [1, 2, 3, 4, 5] + }, + { + field => 'assigned_to', + operator => 'equals', + value => '%group.<1-bug_group>%', + contains => [1, 2, 3, 4, 5] + }, + { + field => 'qa_contact', + operator => 'equals', + value => '%group.<1-bug_group>%', + contains => [1, 2, 3, 4] + }, + { + field => 'cc', + operator => 'equals', + value => '%group.<1-bug_group>%', + contains => [1, 2, 3, 4] + }, + { + field => 'commenter', + operator => 'equals', + value => '%group.<1-bug_group>%', + contains => [1, 2, 3, 4, 5] + }, ); use constant CUSTOM_SEARCH_TESTS => ( - { name => 'OP without CP', contains => [1], - params => [ - { f => 'OP' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - ] - }, + { + name => 'OP without CP', + contains => [1], + params => [{f => 'OP'}, {f => 'bug_id', o => 'equals', v => '<1>'},] + }, - { name => 'Empty OP/CP pair before criteria', contains => [1], - params => [ - { f => 'OP' }, { f => 'CP' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - ] - }, + { + name => 'Empty OP/CP pair before criteria', + contains => [1], + params => + [{f => 'OP'}, {f => 'CP'}, {f => 'bug_id', o => 'equals', v => '<1>'},] + }, - { name => 'Empty OP/CP pair after criteria', contains => [1], - params => [ - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'OP' }, { f => 'CP' }, - ] - }, + { + name => 'Empty OP/CP pair after criteria', + contains => [1], + params => + [{f => 'bug_id', o => 'equals', v => '<1>'}, {f => 'OP'}, {f => 'CP'},] + }, - { name => 'empty OP/CP mid criteria', contains => [1], - columns => ['assigned_to'], - params => [ - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'OP' }, { f => 'CP' }, - { f => 'assigned_to', o => 'substr', v => '@' }, - ] - }, + { + name => 'empty OP/CP mid criteria', + contains => [1], + columns => ['assigned_to'], + params => [ + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'OP'}, + {f => 'CP'}, + {f => 'assigned_to', o => 'substr', v => '@'}, + ] + }, - { name => 'bug_id = 1 AND assigned_to contains @', contains => [1], - columns => ['assigned_to'], - params => [ - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'substr', v => '@' }, - ] - }, + { + name => 'bug_id = 1 AND assigned_to contains @', + contains => [1], + columns => ['assigned_to'], + params => [ + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'substr', v => '@'}, + ] + }, - { name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)', - contains => [3,4,5], - columns => ['assigned_to'], - params => [ - { n => 1, f => 'bug_id', o => 'equals', v => '<1>' }, - { n => 1, f => 'assigned_to', o => 'equals', v => '<2>' }, - ] - }, + { + name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)', + contains => [3, 4, 5], + columns => ['assigned_to'], + params => [ + {n => 1, f => 'bug_id', o => 'equals', v => '<1>'}, + {n => 1, f => 'assigned_to', o => 'equals', v => '<2>'}, + ] + }, - { name => 'bug_id = 1 OR assigned_to = 2', contains => [1,2], - columns => ['assigned_to'], top_params => { j_top => 'OR' }, - params => [ - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'equals', v => '<2>' }, - ] - }, + { + name => 'bug_id = 1 OR assigned_to = 2', + contains => [1, 2], + columns => ['assigned_to'], + top_params => {j_top => 'OR'}, + params => [ + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'equals', v => '<2>'}, + ] + }, - { name => 'NOT(bug_id = 1 AND assigned_to = 1)', contains => [2,3,4,5], - columns => ['assigned_to'], - params => [ - { f => 'OP', n => 1 }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'equals', v => '<1>' }, - { f => 'CP' }, - ] - }, + { + name => 'NOT(bug_id = 1 AND assigned_to = 1)', + contains => [2, 3, 4, 5], + columns => ['assigned_to'], + params => [ + {f => 'OP', n => 1}, + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'equals', v => '<1>'}, + {f => 'CP'}, + ] + }, - { name => '(bug_id = 1 AND assigned_to contains @) ' - . ' OR (bug_id = 2 AND assigned_to contains @)', - contains => [1,2], columns => ['assigned_to'], - top_params => { j_top => 'OR' }, - params => [ - { f => 'OP' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'substr', v => '@' }, - { f => 'CP' }, - { f => 'OP' }, - { f => 'bug_id', o => 'equals', v => '<2>' }, - { f => 'assigned_to', o => 'substr', v => '@' }, - { f => 'CP' }, - ] - }, + { + name => '(bug_id = 1 AND assigned_to contains @) ' + . ' OR (bug_id = 2 AND assigned_to contains @)', + contains => [1, 2], + columns => ['assigned_to'], + top_params => {j_top => 'OR'}, + params => [ + {f => 'OP'}, + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'substr', v => '@'}, + {f => 'CP'}, + {f => 'OP'}, + {f => 'bug_id', o => 'equals', v => '<2>'}, + {f => 'assigned_to', o => 'substr', v => '@'}, + {f => 'CP'}, + ] + }, - { name => '(bug_id = 1 OR assigned_to = 2) ' - . ' AND (bug_id = 2 OR assigned_to = 1)', - contains => [1,2], columns => ['assigned_to'], - params => [ - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'equals', v => '<2>' }, - { f => 'CP' }, - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<2>' }, - { f => 'assigned_to', o => 'equals', v => '<1>' }, - { f => 'CP' }, - ] - }, + { + name => '(bug_id = 1 OR assigned_to = 2) ' + . ' AND (bug_id = 2 OR assigned_to = 1)', + contains => [1, 2], + columns => ['assigned_to'], + params => [ + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'equals', v => '<2>'}, + {f => 'CP'}, + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<2>'}, + {f => 'assigned_to', o => 'equals', v => '<1>'}, + {f => 'CP'}, + ] + }, - { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) ' - . ' AND (bug_id = 2 OR assigned_to = 1) )', - contains => [1,2,3], columns => ['assigned_to'], - top_params => { j_top => 'OR' }, - params => [ - { f => 'bug_id', o => 'equals', v => '<3>' }, - { f => 'OP' }, - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'equals', v => '<2>' }, - { f => 'CP' }, - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<2>' }, - { f => 'assigned_to', o => 'equals', v => '<1>' }, - { f => 'CP' }, - { f => 'CP' }, - ] - }, + { + name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) ' + . ' AND (bug_id = 2 OR assigned_to = 1) )', + contains => [1, 2, 3], + columns => ['assigned_to'], + top_params => {j_top => 'OR'}, + params => [ + {f => 'bug_id', o => 'equals', v => '<3>'}, + {f => 'OP'}, + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'equals', v => '<2>'}, + {f => 'CP'}, + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<2>'}, + {f => 'assigned_to', o => 'equals', v => '<1>'}, + {f => 'CP'}, + {f => 'CP'}, + ] + }, - { name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) ' - . ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4', - contains => [1,2,3,4], columns => ['assigned_to'], - top_params => { j_top => 'OR' }, - params => [ - { f => 'bug_id', o => 'equals', v => '<3>' }, - { f => 'OP' }, - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<1>' }, - { f => 'assigned_to', o => 'equals', v => '<2>' }, - { f => 'CP' }, - { f => 'OP', j => 'OR' }, - { f => 'bug_id', o => 'equals', v => '<2>' }, - { f => 'assigned_to', o => 'equals', v => '<1>' }, - { f => 'CP' }, - { f => 'CP' }, - { f => 'bug_id', o => 'equals', v => '<4>' }, - ] - }, + { + name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) ' + . ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4', + contains => [1, 2, 3, 4], + columns => ['assigned_to'], + top_params => {j_top => 'OR'}, + params => [ + {f => 'bug_id', o => 'equals', v => '<3>'}, + {f => 'OP'}, + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<1>'}, + {f => 'assigned_to', o => 'equals', v => '<2>'}, + {f => 'CP'}, + {f => 'OP', j => 'OR'}, + {f => 'bug_id', o => 'equals', v => '<2>'}, + {f => 'assigned_to', o => 'equals', v => '<1>'}, + {f => 'CP'}, + {f => 'CP'}, + {f => 'bug_id', o => 'equals', v => '<4>'}, + ] + }, ); diff --git a/xt/lib/Bugzilla/Test/Search/CustomTest.pm b/xt/lib/Bugzilla/Test/Search/CustomTest.pm index 132e5ac40..7455d4828 100644 --- a/xt/lib/Bugzilla/Test/Search/CustomTest.pm +++ b/xt/lib/Bugzilla/Test/Search/CustomTest.pm @@ -24,7 +24,7 @@ use Storable qw(dclone); sub new { my ($class, $test, $search_test) = @_; - bless { raw_test => dclone($test), search_test => $search_test }, $class; + bless {raw_test => dclone($test), search_test => $search_test}, $class; } ############# @@ -32,31 +32,33 @@ sub new { ############# sub search_test { return $_[0]->{search_test} } -sub name { return 'Custom: ' . $_[0]->test->{name} } -sub test { return $_[0]->{raw_test} } +sub name { return 'Custom: ' . $_[0]->test->{name} } +sub test { return $_[0]->{raw_test} } sub operator_test { die "unimplemented" } -sub field_object { die "unimplemented" } -sub main_value { die "unimplenmented" } -sub test_value { die "unimplemented" } +sub field_object { die "unimplemented" } +sub main_value { die "unimplenmented" } +sub test_value { die "unimplemented" } + # Custom tests don't use transforms. -sub transformed_value_was_equal { 0 } +sub transformed_value_was_equal {0} + sub debug_value { - my ($self) = @_; - my $string = ''; - my $params = $self->search_params; - foreach my $param (keys %$params) { - $string .= $param . "=" . $params->{$param} . '&'; - } - chop($string); - return $string; + my ($self) = @_; + my $string = ''; + my $params = $self->search_params; + foreach my $param (keys %$params) { + $string .= $param . "=" . $params->{$param} . '&'; + } + chop($string); + return $string; } # The tests we know are broken for this operator/field combination. sub _known_broken { return {} } -sub contains_known_broken { return undef } -sub search_known_broken { return undef } -sub field_not_yet_implemented { return undef } +sub contains_known_broken { return undef } +sub search_known_broken { return undef } +sub field_not_yet_implemented { return undef } sub invalid_field_operator_combination { return undef } ######################################### @@ -66,36 +68,35 @@ sub invalid_field_operator_combination { return undef } # Converts the f, o, v rows into f0, o0, v0, etc. and translates # the values appropriately. sub search_params { - my ($self) = @_; - - my %params = %{ $self->test->{top_params} || {} }; - my $counter = 0; - foreach my $row (@{ $self->test->{params} }) { - $row->{v} = $self->translate_value($row) if exists $row->{v}; - foreach my $key (keys %$row) { - $params{"${key}$counter"} = $row->{$key}; - } - $counter++; + my ($self) = @_; + + my %params = %{$self->test->{top_params} || {}}; + my $counter = 0; + foreach my $row (@{$self->test->{params}}) { + $row->{v} = $self->translate_value($row) if exists $row->{v}; + foreach my $key (keys %$row) { + $params{"${key}$counter"} = $row->{$key}; } + $counter++; + } - return \%params; + return \%params; } sub translate_value { - my ($self, $row) = @_; - my $as_test = { field => $row->{f}, operator => $row->{o}, - value => $row->{v} }; - my $operator_test = new Bugzilla::Test::Search::OperatorTest($row->{o}, - $self->search_test); - my $field = Bugzilla::Field->check($row->{f}); - my $field_test = new Bugzilla::Test::Search::FieldTest($operator_test, - $field, $as_test); - return $field_test->translated_value; + my ($self, $row) = @_; + my $as_test = {field => $row->{f}, operator => $row->{o}, value => $row->{v}}; + my $operator_test + = new Bugzilla::Test::Search::OperatorTest($row->{o}, $self->search_test); + my $field = Bugzilla::Field->check($row->{f}); + my $field_test + = new Bugzilla::Test::Search::FieldTest($operator_test, $field, $as_test); + return $field_test->translated_value; } sub search_columns { - my ($self) = @_; - return ['bug_id', @{ $self->test->{columns} || [] }]; + my ($self) = @_; + return ['bug_id', @{$self->test->{columns} || []}]; } 1; diff --git a/xt/lib/Bugzilla/Test/Search/FieldTest.pm b/xt/lib/Bugzilla/Test/Search/FieldTest.pm index 5e86d92e2..2ccaab735 100644 --- a/xt/lib/Bugzilla/Test/Search/FieldTest.pm +++ b/xt/lib/Bugzilla/Test/Search/FieldTest.pm @@ -26,10 +26,12 @@ use Test::Exception; ############### sub new { - my ($class, $operator_test, $field, $test) = @_; - return bless { operator_test => $operator_test, - field_object => $field, - raw_test => $test }, $class; + my ($class, $operator_test, $field, $test) = @_; + return bless { + operator_test => $operator_test, + field_object => $field, + raw_test => $test + }, $class; } ############# @@ -40,144 +42,156 @@ sub num_tests { return TESTS_PER_RUN } # The Bugzilla::Test::Search::OperatorTest that this is a child of. sub operator_test { return $_[0]->{operator_test} } + # The Bugzilla::Field being tested. sub field_object { return $_[0]->{field_object} } + # The name of the field being tested, which we need much more often # than we need the object. sub field { - my ($self) = @_; - $self->{field_name} ||= $self->field_object->name; - return $self->{field_name}; + my ($self) = @_; + $self->{field_name} ||= $self->field_object->name; + return $self->{field_name}; } + # The Bugzilla::Test::Search object that this is a child of. sub search_test { return $_[0]->operator_test->search_test } + # The operator being tested sub operator { return $_[0]->operator_test->operator } + # The bugs currently being tested by Bugzilla::Test::Search. sub bugs { return $_[0]->search_test->bugs } + sub bug { - my $self = shift; - return $self->search_test->bug(@_); + my $self = shift; + return $self->search_test->bug(@_); } + sub number { - my ($self, $id) = @_; - foreach my $number (1..NUM_BUGS) { - return $number if $self->search_test->bug($number)->id == $id; - } - return 0; + my ($self, $id) = @_; + foreach my $number (1 .. NUM_BUGS) { + return $number if $self->search_test->bug($number)->id == $id; + } + return 0; } # The name displayed for this test by Test::More. Used in test descriptions. sub name { - my ($self) = @_; - my $field = $self->field; - my $operator = $self->operator; - my $value = $self->main_value; - - my $name = "$field-$operator-$value"; - if (my $extra_name = $self->test->{extra_name}) { - $name .= "-$extra_name"; - } - return $name; + my ($self) = @_; + my $field = $self->field; + my $operator = $self->operator; + my $value = $self->main_value; + + my $name = "$field-$operator-$value"; + if (my $extra_name = $self->test->{extra_name}) { + $name .= "-$extra_name"; + } + return $name; } # The appropriate value from the TESTS constant for this test, taking # into account overrides. sub test { - my $self = shift; - return $self->{test} if $self->{test}; - - my %test = %{ $self->{raw_test} }; - - # We have field name overrides... - my $override = $test{override}->{$self->field}; - # And also field type overrides. - if (!$override) { - $override = $test{override}->{$self->field_object->type} || {}; - } - - foreach my $key (%$override) { - $test{$key} = $override->{$key}; - } - - $self->{test} = \%test; - return $self->{test}; + my $self = shift; + return $self->{test} if $self->{test}; + + my %test = %{$self->{raw_test}}; + + # We have field name overrides... + my $override = $test{override}->{$self->field}; + + # And also field type overrides. + if (!$override) { + $override = $test{override}->{$self->field_object->type} || {}; + } + + foreach my $key (%$override) { + $test{$key} = $override->{$key}; + } + + $self->{test} = \%test; + return $self->{test}; } # All the values for all the bugs for this field. sub _field_values { - my ($self) = @_; - return $self->{field_values} if $self->{field_values}; - - my %field_values; - foreach my $number (1..NUM_BUGS) { - $field_values{$number} = $self->_field_values_for_bug($number); - } - $self->{field_values} = \%field_values; - return $self->{field_values}; + my ($self) = @_; + return $self->{field_values} if $self->{field_values}; + + my %field_values; + foreach my $number (1 .. NUM_BUGS) { + $field_values{$number} = $self->_field_values_for_bug($number); + } + $self->{field_values} = \%field_values; + return $self->{field_values}; } + # The values for this field for the numbered bug. sub bug_values { - my ($self, $number) = @_; - return @{ $self->_field_values->{$number} }; + my ($self, $number) = @_; + return @{$self->_field_values->{$number}}; } # The untranslated, non-overriden value--used in the name of the test # and other places. sub main_value { return $_[0]->{raw_test}->{value} } + # The untranslated test value, taking into account overrides. -sub test_value { return $_[0]->test->{value} }; +sub test_value { return $_[0]->test->{value} } + # The value translated appropriately for passing to Bugzilla::Search. sub translated_value { - my $self = shift; - if (!exists $self->{translated_value}) { - my $value = $self->search_test->value_translation_cache($self); - if (!defined $value) { - $value = $self->_translate_value(); - $self->search_test->value_translation_cache($self, $value); - } - $self->{translated_value} = $value; + my $self = shift; + if (!exists $self->{translated_value}) { + my $value = $self->search_test->value_translation_cache($self); + if (!defined $value) { + $value = $self->_translate_value(); + $self->search_test->value_translation_cache($self, $value); } - return $self->{translated_value}; + $self->{translated_value} = $value; + } + return $self->{translated_value}; } + # Used in failure diagnostic messages. sub debug_fail { - my ($self, $number, $results, $sql) = @_; - my @expected = @{ $self->test->{contains} }; - my @results = sort - map { $self->number($_) } - map { $_->[0] } - @$results; - return - " Value: '" . $self->translated_value . "'\n" . - "Expected: [" . join(',', @expected) . "]\n" . - " Results: [" . join(',', @results) . "]\n" . - trim($sql) . "\n"; + my ($self, $number, $results, $sql) = @_; + my @expected = @{$self->test->{contains}}; + my @results = sort map { $self->number($_) } map { $_->[0] } @$results; + return + " Value: '" + . $self->translated_value . "'\n" + . "Expected: [" + . join(',', @expected) . "]\n" + . " Results: [" + . join(',', @results) . "]\n" + . trim($sql) . "\n"; } # True for a bug if we ran the "transform" function on it and the # result was equal to its first value. sub transformed_value_was_equal { - my ($self, $number, $value) = @_; - if (@_ > 2) { - $self->{transformed_value_was_equal}->{$number} = $value; - $self->search_test->was_equal_cache($self, $number, $value); - } - my $cached = $self->search_test->was_equal_cache($self, $number); - return $cached if defined $cached; - return $self->{transformed_value_was_equal}->{$number}; + my ($self, $number, $value) = @_; + if (@_ > 2) { + $self->{transformed_value_was_equal}->{$number} = $value; + $self->search_test->was_equal_cache($self, $number, $value); + } + my $cached = $self->search_test->was_equal_cache($self, $number); + return $cached if defined $cached; + return $self->{transformed_value_was_equal}->{$number}; } # True if this test is supposed to contain the numbered bug. sub bug_is_contained { - my ($self, $number) = @_; - my $contains = $self->test->{contains}; - if ($self->transformed_value_was_equal($number) - and !$self->test->{override}->{$self->field}->{contains}) - { - $contains = $self->test->{if_equal}->{contains}; - } - return grep($_ == $number, @$contains) ? 1 : 0; + my ($self, $number) = @_; + my $contains = $self->test->{contains}; + if ($self->transformed_value_was_equal($number) + and !$self->test->{override}->{$self->field}->{contains}) + { + $contains = $self->test->{if_equal}->{contains}; + } + return grep($_ == $number, @$contains) ? 1 : 0; } ################################################### @@ -186,112 +200,114 @@ sub bug_is_contained { # The tests we know are broken for this operator/field combination. sub _known_broken { - my ($self, $constant, $skip_pg_check) = @_; - - $constant ||= KNOWN_BROKEN; - my $field = $self->field; - my $type = $self->field_object->type; - my $operator = $self->operator; - my $value = $self->main_value; - my $value_name = "$operator-$value"; - if (my $extra_name = $self->test->{extra_name}) { - $value_name .= "-$extra_name"; - } - - my $value_broken = $constant->{$value_name}->{$field}; - $value_broken ||= $constant->{$value_name}->{$type}; - return $value_broken if $value_broken; - my $operator_broken = $constant->{$operator}->{$field}; - $operator_broken ||= $constant->{$operator}->{$type}; - return $operator_broken if $operator_broken; - return {}; + my ($self, $constant, $skip_pg_check) = @_; + + $constant ||= KNOWN_BROKEN; + my $field = $self->field; + my $type = $self->field_object->type; + my $operator = $self->operator; + my $value = $self->main_value; + my $value_name = "$operator-$value"; + if (my $extra_name = $self->test->{extra_name}) { + $value_name .= "-$extra_name"; + } + + my $value_broken = $constant->{$value_name}->{$field}; + $value_broken ||= $constant->{$value_name}->{$type}; + return $value_broken if $value_broken; + my $operator_broken = $constant->{$operator}->{$field}; + $operator_broken ||= $constant->{$operator}->{$type}; + return $operator_broken if $operator_broken; + return {}; } # True if the "contains" search for the numbered bug is broken. # That is, either the result is supposed to contain it and doesn't, # or the result is not supposed to contain it and does. sub contains_known_broken { - my ($self, $number) = @_; - my $field = $self->field; - my $operator = $self->operator; + my ($self, $number) = @_; + my $field = $self->field; + my $operator = $self->operator; - my $contains_broken = $self->_known_broken->{contains} || []; - if (grep($_ == $number, @$contains_broken)) { - return "$field $operator contains $number is known to be broken"; - } - return undef; + my $contains_broken = $self->_known_broken->{contains} || []; + if (grep($_ == $number, @$contains_broken)) { + return "$field $operator contains $number is known to be broken"; + } + return undef; } # Used by subclasses. Checks both bug_is_contained and contains_known_broken # to tell you whether or not the bug will *actually* be found by the test. sub will_actually_contain_bug { - my ($self, $number) = @_; - my $is_contained = $self->bug_is_contained($number) ? 1 : 0; - my $is_broken = $self->contains_known_broken($number) ? 1 : 0; + my ($self, $number) = @_; + my $is_contained = $self->bug_is_contained($number) ? 1 : 0; + my $is_broken = $self->contains_known_broken($number) ? 1 : 0; + + # If the test is supposed to contain the bug and *isn't* broken, + # then the test will contain the bug. + return 1 if ($is_contained and !$is_broken); - # If the test is supposed to contain the bug and *isn't* broken, - # then the test will contain the bug. - return 1 if ($is_contained and !$is_broken); - # If this test is *not* supposed to contain the bug, but that test is - # broken, then this test *will* contain the bug. - return 1 if (!$is_contained and $is_broken); + # If this test is *not* supposed to contain the bug, but that test is + # broken, then this test *will* contain the bug. + return 1 if (!$is_contained and $is_broken); - return 0; + return 0; } # Returns a string if creating a Bugzilla::Search object throws an error, # with this field/operator/value combination. sub search_known_broken { - my ($self) = @_; - my $field = $self->field; - my $operator = $self->operator; - if ($self->_known_broken->{search}) { - return "Bugzilla::Search for $field $operator is known to be broken"; - } - return undef; + my ($self) = @_; + my $field = $self->field; + my $operator = $self->operator; + if ($self->_known_broken->{search}) { + return "Bugzilla::Search for $field $operator is known to be broken"; + } + return undef; } - + # Returns a string if we haven't yet implemented the tests for this field, # but we plan to in the future. sub field_not_yet_implemented { - my ($self) = @_; - my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS; - if ($skip_this_field) { - my $field = $self->field; - return "$field testing not yet implemented"; - } - return undef; + my ($self) = @_; + my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS; + if ($skip_this_field) { + my $field = $self->field; + return "$field testing not yet implemented"; + } + return undef; } # Returns a message if this field/operator combination can't ever be run. # At no time in the future will this field/operator combination ever work. sub invalid_field_operator_combination { - my ($self) = @_; - my $field = $self->field; - my $operator = $self->operator; - - if ($field eq 'content' && $operator !~ /matches/) { - return "content field does not support $operator"; - } - elsif ($operator =~ /matches/ && $field ne 'content') { - return "matches operator does not support fields other than content"; - } - return undef; + my ($self) = @_; + my $field = $self->field; + my $operator = $self->operator; + + if ($field eq 'content' && $operator !~ /matches/) { + return "content field does not support $operator"; + } + elsif ($operator =~ /matches/ && $field ne 'content') { + return "matches operator does not support fields other than content"; + } + return undef; } # True if this field is broken in an OR combination. sub join_broken { - my ($self, $or_broken_map) = @_; - my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator}; - if (!$or_broken) { - # See if this is a comment field, and in that case, if there's - # a generic entry for all comment fields. - my $is_comment_field = COMMENT_FIELDS->{$self->field}; - if ($is_comment_field) { - $or_broken = $or_broken_map->{'longdescs.-' . $self->operator}; - } + my ($self, $or_broken_map) = @_; + my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator}; + if (!$or_broken) { + + # See if this is a comment field, and in that case, if there's + # a generic entry for all comment fields. + my $is_comment_field = COMMENT_FIELDS->{$self->field}; + if ($is_comment_field) { + $or_broken = $or_broken_map->{'longdescs.-' . $self->operator}; } - return $or_broken; + } + return $or_broken; } ######################################### @@ -300,28 +316,28 @@ sub join_broken { # The data that will get passed to Bugzilla::Search as its arguments. sub search_params { - my ($self) = @_; - return $self->{search_params} if $self->{search_params}; + my ($self) = @_; + return $self->{search_params} if $self->{search_params}; + + my %params = ( + "field0-0-0" => $self->field, + "type0-0-0" => $self->operator, + "value0-0-0" => $self->translated_value, + ); - my %params = ( - "field0-0-0" => $self->field, - "type0-0-0" => $self->operator, - "value0-0-0" => $self->translated_value, - ); - - $self->{search_params} = \%params; - return $self->{search_params}; + $self->{search_params} = \%params; + return $self->{search_params}; } sub search_columns { - my ($self) = @_; - my $field = $self->field; - my @search_fields = qw(bug_id); - if ($self->field_object->buglist) { - my $col_name = COLUMN_TRANSLATION->{$field} || $field; - push(@search_fields, $col_name); - } - return \@search_fields; + my ($self) = @_; + my $field = $self->field; + my @search_fields = qw(bug_id); + if ($self->field_object->buglist) { + my $col_name = COLUMN_TRANSLATION->{$field} || $field; + push(@search_fields, $col_name); + } + return \@search_fields; } @@ -330,103 +346,107 @@ sub search_columns { ################ sub _field_values_for_bug { - my ($self, $number) = @_; - my $field = $self->field; - - my @values; - - if ($field =~ /^attach.+\.(.+)$/ ) { - my $attach_field = $1; - $attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field; - @values = $self->_values_for($number, 'attachments', $attach_field); - } - elsif (my $flag_field = FLAG_FIELDS->{$field}) { - @values = $self->_values_for($number, 'flags', $flag_field); - } - elsif (my $translation = COMMENT_FIELDS->{$field}) { - @values = $self->_values_for($number, 'comments', $translation); - # We want the last value to come first, so that single-value - # searches use the last comment. - @values = reverse @values; - } - elsif ($field eq 'longdescs.count') { - @values = scalar(@{ $self->bug($number)->comments }); - } - elsif ($field eq 'work_time') { - @values = $self->_values_for($number, 'actual_time'); - } - elsif ($field eq 'bug_group') { - @values = $self->_values_for($number, 'groups_in', 'name'); - } - elsif ($field eq 'keywords') { - @values = $self->_values_for($number, 'keyword_objects', 'name'); - } - elsif ($field eq 'content') { - @values = $self->_values_for($number, 'short_desc'); - } - elsif ($field eq 'see_also') { - @values = $self->_values_for($number, 'see_also', 'name'); - } - elsif ($field eq 'tag') { - @values = $self->_values_for($number, 'tags'); - } - # Bugzilla::Bug truncates creation_ts, but we need the full value - # from the database. This has no special value for changedfrom, - # because it never changes. - elsif ($field eq 'creation_ts') { - my $bug = $self->bug($number); - my $creation_ts = Bugzilla->dbh->selectrow_array( - 'SELECT creation_ts FROM bugs WHERE bug_id = ?', - undef, $bug->id); - @values = ($creation_ts); - } - else { - @values = $self->_values_for($number, $field); - } - - # We convert user objects to their login name, here, all in one - # block for simplicity. - if (grep { $_ eq $field } USER_FIELDS) { - # requestees.login_name is empty for most bugs (but checking - # blessed(undef) handles that. - # Values that come from %original_values aren't User objects. - @values = map { blessed($_) ? $_->login : $_ } @values; - @values = grep { defined $_ } @values; - } - - return \@values; + my ($self, $number) = @_; + my $field = $self->field; + + my @values; + + if ($field =~ /^attach.+\.(.+)$/) { + my $attach_field = $1; + $attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field; + @values = $self->_values_for($number, 'attachments', $attach_field); + } + elsif (my $flag_field = FLAG_FIELDS->{$field}) { + @values = $self->_values_for($number, 'flags', $flag_field); + } + elsif (my $translation = COMMENT_FIELDS->{$field}) { + @values = $self->_values_for($number, 'comments', $translation); + + # We want the last value to come first, so that single-value + # searches use the last comment. + @values = reverse @values; + } + elsif ($field eq 'longdescs.count') { + @values = scalar(@{$self->bug($number)->comments}); + } + elsif ($field eq 'work_time') { + @values = $self->_values_for($number, 'actual_time'); + } + elsif ($field eq 'bug_group') { + @values = $self->_values_for($number, 'groups_in', 'name'); + } + elsif ($field eq 'keywords') { + @values = $self->_values_for($number, 'keyword_objects', 'name'); + } + elsif ($field eq 'content') { + @values = $self->_values_for($number, 'short_desc'); + } + elsif ($field eq 'see_also') { + @values = $self->_values_for($number, 'see_also', 'name'); + } + elsif ($field eq 'tag') { + @values = $self->_values_for($number, 'tags'); + } + + # Bugzilla::Bug truncates creation_ts, but we need the full value + # from the database. This has no special value for changedfrom, + # because it never changes. + elsif ($field eq 'creation_ts') { + my $bug = $self->bug($number); + my $creation_ts + = Bugzilla->dbh->selectrow_array( + 'SELECT creation_ts FROM bugs WHERE bug_id = ?', + undef, $bug->id); + @values = ($creation_ts); + } + else { + @values = $self->_values_for($number, $field); + } + + # We convert user objects to their login name, here, all in one + # block for simplicity. + if (grep { $_ eq $field } USER_FIELDS) { + + # requestees.login_name is empty for most bugs (but checking + # blessed(undef) handles that. + # Values that come from %original_values aren't User objects. + @values = map { blessed($_) ? $_->login : $_ } @values; + @values = grep { defined $_ } @values; + } + + return \@values; } sub _values_for { - my ($self, $number, $bug_field, $item_field) = @_; + my ($self, $number, $bug_field, $item_field) = @_; - my $item; - if ($self->operator eq 'changedfrom') { - $item = $self->search_test->bug_create_value($number, $bug_field); - } - else { - my $bug = $self->bug($number); - $item = $bug->$bug_field; - } + my $item; + if ($self->operator eq 'changedfrom') { + $item = $self->search_test->bug_create_value($number, $bug_field); + } + else { + my $bug = $self->bug($number); + $item = $bug->$bug_field; + } - if ($item_field) { - if ($bug_field eq 'flags' and $item_field eq 'name') { - return (map { $_->name . $_->status } @$item); - } - return (map { $self->_get_item($_, $item_field) } @$item); + if ($item_field) { + if ($bug_field eq 'flags' and $item_field eq 'name') { + return (map { $_->name . $_->status } @$item); } + return (map { $self->_get_item($_, $item_field) } @$item); + } - return @$item if ref($item) eq 'ARRAY'; - return $item if defined $item; - return (); + return @$item if ref($item) eq 'ARRAY'; + return $item if defined $item; + return (); } sub _get_item { - my ($self, $from, $field) = @_; - if (blessed($from)) { - return $from->$field; - } - return $from->{$field}; + my ($self, $from, $field) = @_; + if (blessed($from)) { + return $from->$field; + } + return $from->{$field}; } ##################### @@ -439,83 +459,85 @@ sub _get_item { # and then we insert it as required into the "value" from TESTS. (For example, # <1> becomes the value for the field from bug 1.) sub _translate_value { - my $self = shift; - my $value = $self->test_value; - foreach my $number (1..NUM_BUGS) { - $value = $self->_translate_value_for_bug($number, $value); - } - # Sanity check to make sure that none of the <> stuff was left in. - if ($value =~ /<\d/) { - die $self->name . ": value untranslated: $value\n"; - } - return $value; + my $self = shift; + my $value = $self->test_value; + foreach my $number (1 .. NUM_BUGS) { + $value = $self->_translate_value_for_bug($number, $value); + } + + # Sanity check to make sure that none of the <> stuff was left in. + if ($value =~ /<\d/) { + die $self->name . ": value untranslated: $value\n"; + } + return $value; } sub _translate_value_for_bug { - my ($self, $number, $value) = @_; - - my $bug = $self->bug($number); - - my $bug_id = $bug->id; - $value =~ s/<$number-id>/$bug_id/g; - my $bug_delta = $bug->delta_ts; - $value =~ s/<$number-delta>/$bug_delta/g; - my $reporter = $bug->reporter->login; - $value =~ s/<$number-reporter>/$reporter/g; - if ($value =~ /<$number-bug_group>/) { - my @bug_groups = map { $_->name } @{ $bug->groups_in }; - @bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups; - my $group = $bug_groups[0]; - $value =~ s/<$number-bug_group>/$group/g; - } - - my @bug_values = $self->bug_values($number); - return $value if !@bug_values; - - if ($self->operator =~ /substr/) { - @bug_values = map { $self->_substr_value($_) } @bug_values; - } - - my $string_value = $bug_values[0]; - if ($self->operator =~ /word/) { - $string_value = join(' ', @bug_values); - } - if (my $func = $self->test->{transform}) { - my $transformed = $func->(@bug_values); - my $is_equal = $transformed eq $bug_values[0] ? 1 : 0; - $self->transformed_value_was_equal($number, $is_equal); - $string_value = $transformed; - } - - if ($self->test->{escape}) { - $string_value = quotemeta($string_value); - } - $value =~ s/<$number>/$string_value/g; - - return $value; + my ($self, $number, $value) = @_; + + my $bug = $self->bug($number); + + my $bug_id = $bug->id; + $value =~ s/<$number-id>/$bug_id/g; + my $bug_delta = $bug->delta_ts; + $value =~ s/<$number-delta>/$bug_delta/g; + my $reporter = $bug->reporter->login; + $value =~ s/<$number-reporter>/$reporter/g; + if ($value =~ /<$number-bug_group>/) { + my @bug_groups = map { $_->name } @{$bug->groups_in}; + @bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups; + my $group = $bug_groups[0]; + $value =~ s/<$number-bug_group>/$group/g; + } + + my @bug_values = $self->bug_values($number); + return $value if !@bug_values; + + if ($self->operator =~ /substr/) { + @bug_values = map { $self->_substr_value($_) } @bug_values; + } + + my $string_value = $bug_values[0]; + if ($self->operator =~ /word/) { + $string_value = join(' ', @bug_values); + } + if (my $func = $self->test->{transform}) { + my $transformed = $func->(@bug_values); + my $is_equal = $transformed eq $bug_values[0] ? 1 : 0; + $self->transformed_value_was_equal($number, $is_equal); + $string_value = $transformed; + } + + if ($self->test->{escape}) { + $string_value = quotemeta($string_value); + } + $value =~ s/<$number>/$string_value/g; + + return $value; } sub _substr_value { - my ($self, $value) = @_; - my $field = $self->field; - my $type = $self->field_object->type; - my $substr_size = SUBSTR_SIZE; - if (exists FIELD_SUBSTR_SIZE->{$field}) { - $substr_size = FIELD_SUBSTR_SIZE->{$field}; - } - elsif (exists FIELD_SUBSTR_SIZE->{$type}) { - $substr_size = FIELD_SUBSTR_SIZE->{$type}; - } - if ($substr_size > 0) { - # The field name is included in every field value, and if it's - # long, it might take up the whole substring, and we don't want that. - if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) { - $substr_size += length($field); - } - my $string = substr($value, 0, $substr_size); - return $string; - } - return substr($value, $substr_size); + my ($self, $value) = @_; + my $field = $self->field; + my $type = $self->field_object->type; + my $substr_size = SUBSTR_SIZE; + if (exists FIELD_SUBSTR_SIZE->{$field}) { + $substr_size = FIELD_SUBSTR_SIZE->{$field}; + } + elsif (exists FIELD_SUBSTR_SIZE->{$type}) { + $substr_size = FIELD_SUBSTR_SIZE->{$type}; + } + if ($substr_size > 0) { + + # The field name is included in every field value, and if it's + # long, it might take up the whole substring, and we don't want that. + if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) { + $substr_size += length($field); + } + my $string = substr($value, 0, $substr_size); + return $string; + } + return substr($value, $substr_size); } ##################### @@ -523,95 +545,93 @@ sub _substr_value { ##################### sub run { - my ($self) = @_; - - my $invalid_combination = $self->invalid_field_operator_combination; - my $field_not_implemented = $self->field_not_yet_implemented; - - SKIP: { - skip($invalid_combination, $self->num_tests) if $invalid_combination; - TODO: { - todo_skip ($field_not_implemented, $self->num_tests) if $field_not_implemented; - $self->do_tests(); - } + my ($self) = @_; + + my $invalid_combination = $self->invalid_field_operator_combination; + my $field_not_implemented = $self->field_not_yet_implemented; + +SKIP: { + skip($invalid_combination, $self->num_tests) if $invalid_combination; + TODO: { + todo_skip($field_not_implemented, $self->num_tests) if $field_not_implemented; + $self->do_tests(); } + } } sub do_tests { - my ($self) = @_; - my $name = $self->name; + my ($self) = @_; + my $name = $self->name; - my $search_broken = $self->search_known_broken; - - my $search = $self->_test_search_object_creation(); + my $search_broken = $self->search_known_broken; - my $sql; - TODO: { - local $TODO = $search_broken if $search_broken; - lives_ok { $sql = $search->_sql } "$name: generate SQL"; - } - - my $results; - SKIP: { - skip "Can't run SQL without any SQL", 1 if !defined $sql; - $results = $self->_test_sql($search); - } + my $search = $self->_test_search_object_creation(); + + my $sql; +TODO: { + local $TODO = $search_broken if $search_broken; + lives_ok { $sql = $search->_sql } "$name: generate SQL"; + } - $self->_test_content($results, $sql); + my $results; +SKIP: { + skip "Can't run SQL without any SQL", 1 if !defined $sql; + $results = $self->_test_sql($search); + } + + $self->_test_content($results, $sql); } sub _test_search_object_creation { - my ($self) = @_; - my $name = $self->name; - my @args = (fields => $self->search_columns, params => $self->search_params); - my $search; - lives_ok { $search = new Bugzilla::Search(@args) } - "$name: create search object"; - return $search; + my ($self) = @_; + my $name = $self->name; + my @args = (fields => $self->search_columns, params => $self->search_params); + my $search; + lives_ok { $search = new Bugzilla::Search(@args) } + "$name: create search object"; + return $search; } sub _test_sql { - my ($self, $search) = @_; - my $name = $self->name; - my $results; - lives_ok { $results = $search->data } "$name: Run SQL Query" - or diag($search->_sql); - return $results; + my ($self, $search) = @_; + my $name = $self->name; + my $results; + lives_ok { $results = $search->data } "$name: Run SQL Query" + or diag($search->_sql); + return $results; } sub _test_content { - my ($self, $results, $sql) = @_; + my ($self, $results, $sql) = @_; - SKIP: { - skip "Without results we can't test them", NUM_BUGS if !$results; - foreach my $number (1..NUM_BUGS) { - $self->_test_content_for_bug($number, $results, $sql); - } +SKIP: { + skip "Without results we can't test them", NUM_BUGS if !$results; + foreach my $number (1 .. NUM_BUGS) { + $self->_test_content_for_bug($number, $results, $sql); } + } } sub _test_content_for_bug { - my ($self, $number, $results, $sql) = @_; - my $name = $self->name; - - my $contains_known_broken = $self->contains_known_broken($number); - - my %result_ids = map { $_->[0] => 1 } @$results; - my $bug_id = $self->bug($number)->id; - - TODO: { - local $TODO = $contains_known_broken if $contains_known_broken; - if ($self->bug_is_contained($number)) { - ok($result_ids{$bug_id}, - "$name: contains bug $number ($bug_id)") - or diag $self->debug_fail($number, $results, $sql); - } - else { - ok(!$result_ids{$bug_id}, - "$name: does not contain bug $number ($bug_id)") - or diag $self->debug_fail($number, $results, $sql); - } + my ($self, $number, $results, $sql) = @_; + my $name = $self->name; + + my $contains_known_broken = $self->contains_known_broken($number); + + my %result_ids = map { $_->[0] => 1 } @$results; + my $bug_id = $self->bug($number)->id; + +TODO: { + local $TODO = $contains_known_broken if $contains_known_broken; + if ($self->bug_is_contained($number)) { + ok($result_ids{$bug_id}, "$name: contains bug $number ($bug_id)") + or diag $self->debug_fail($number, $results, $sql); + } + else { + ok(!$result_ids{$bug_id}, "$name: does not contain bug $number ($bug_id)") + or diag $self->debug_fail($number, $results, $sql); } + } } 1; diff --git a/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm b/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm index 888e7eb13..101c09053 100644 --- a/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm +++ b/xt/lib/Bugzilla/Test/Search/FieldTestNormal.pm @@ -15,9 +15,9 @@ use parent qw(Bugzilla::Test::Search::FieldTest); use Scalar::Util qw(blessed); use constant CH_OPERATOR => { - changedafter => 'chfieldfrom', - changedbefore => 'chfieldto', - changedto => 'chfieldvalue', + changedafter => 'chfieldfrom', + changedbefore => 'chfieldto', + changedto => 'chfieldvalue', }; use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter); @@ -27,78 +27,79 @@ use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter); # sometimes (like in Bugzilla::Test::Search's direct code) we just want # to create a FieldTestNormal. sub new { - my $class = shift; - my ($first_arg) = @_; - if (blessed $first_arg - and $first_arg->isa('Bugzilla::Test::Search::FieldTest')) - { - my $self = { %$first_arg }; - return bless $self, $class; - } - return $class->SUPER::new(@_); + my $class = shift; + my ($first_arg) = @_; + if (blessed $first_arg and $first_arg->isa('Bugzilla::Test::Search::FieldTest')) + { + my $self = {%$first_arg}; + return bless $self, $class; + } + return $class->SUPER::new(@_); } sub name { - my $self = shift; - my $name = $self->SUPER::name(@_); - return "$name (Normal Params)"; + my $self = shift; + my $name = $self->SUPER::name(@_); + return "$name (Normal Params)"; } sub search_columns { - my $self = shift; - my $field = $self->field; - # For the assigned_to, qa_contact, and reporter fields, have the - # "Normal Params" test check that the _realname columns work - # all by themselves. - if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) { - return ['bug_id', "${field}_realname"] - } - return $self->SUPER::search_columns(@_); + my $self = shift; + my $field = $self->field; + + # For the assigned_to, qa_contact, and reporter fields, have the + # "Normal Params" test check that the _realname columns work + # all by themselves. + if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) { + return ['bug_id', "${field}_realname"]; + } + return $self->SUPER::search_columns(@_); } sub search_params { - my ($self) = @_; - my $field = $self->field; - my $operator = $self->operator; - my $value = $self->translated_value; - if ($operator eq 'anyexact') { - $value = [split ',', $value]; - } - - if (my $ch_param = CH_OPERATOR->{$operator}) { - if ($field eq 'creation_ts') { - $field = '[Bug creation]'; - } - return { chfield => $field, $ch_param => $value }; - } - - if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') { - return { chfieldfrom => $value }; - } - if ($field eq 'delta_ts' and $operator eq 'lessthaneq') { - return { chfieldto => $value }; - } - - if ($field eq 'deadline' and $operator eq 'greaterthaneq') { - return { deadlinefrom => $value }; - } - if ($field eq 'deadline' and $operator eq 'lessthaneq') { - return { deadlineto => $value }; - } - - if (grep { $_ eq $field } EMAIL_FIELDS) { - $field = 'longdesc' if $field eq 'commenter'; - return { - email1 => $value, - "email${field}1" => 1, - emailtype1 => $operator, - # Used to do extra tests on special sorts of email* combinations. - %{ $self->test->{extra_params} || {} }, - }; + my ($self) = @_; + my $field = $self->field; + my $operator = $self->operator; + my $value = $self->translated_value; + if ($operator eq 'anyexact') { + $value = [split ',', $value]; + } + + if (my $ch_param = CH_OPERATOR->{$operator}) { + if ($field eq 'creation_ts') { + $field = '[Bug creation]'; } + return {chfield => $field, $ch_param => $value}; + } + + if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') { + return {chfieldfrom => $value}; + } + if ($field eq 'delta_ts' and $operator eq 'lessthaneq') { + return {chfieldto => $value}; + } + + if ($field eq 'deadline' and $operator eq 'greaterthaneq') { + return {deadlinefrom => $value}; + } + if ($field eq 'deadline' and $operator eq 'lessthaneq') { + return {deadlineto => $value}; + } + + if (grep { $_ eq $field } EMAIL_FIELDS) { + $field = 'longdesc' if $field eq 'commenter'; + return { + email1 => $value, + "email${field}1" => 1, + emailtype1 => $operator, + + # Used to do extra tests on special sorts of email* combinations. + %{$self->test->{extra_params} || {}}, + }; + } - $field =~ s/\./_/g; - return { $field => $value, "${field}_type" => $operator }; + $field =~ s/\./_/g; + return {$field => $value, "${field}_type" => $operator}; } 1; diff --git a/xt/lib/Bugzilla/Test/Search/InjectionTest.pm b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm index 90eaabc78..f34acc6bb 100644 --- a/xt/lib/Bugzilla/Test/Search/InjectionTest.pm +++ b/xt/lib/Bugzilla/Test/Search/InjectionTest.pm @@ -18,60 +18,62 @@ use Test::Exception; sub num_tests { return NUM_SEARCH_TESTS } sub _known_broken { - my ($self) = @_; - my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator}; - # We don't want to auto-vivify $operator_broken and thus make it true. - my @field_ok = $operator_broken ? @{ $operator_broken->{field_ok} || [] } - : (); - $operator_broken = undef if grep { $_ eq $self->field } @field_ok; - - my $field_broken = INJECTION_BROKEN_FIELD->{$self->field} - || INJECTION_BROKEN_FIELD->{$self->field_object->type}; - # We don't want to auto-vivify $field_broken and thus make it true. - my @operator_ok = $field_broken ? @{ $field_broken->{operator_ok} || [] } - : (); - $field_broken = undef if grep { $_ eq $self->operator } @operator_ok; - - return $operator_broken || $field_broken || {}; + my ($self) = @_; + my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator}; + + # We don't want to auto-vivify $operator_broken and thus make it true. + my @field_ok = $operator_broken ? @{$operator_broken->{field_ok} || []} : (); + $operator_broken = undef if grep { $_ eq $self->field } @field_ok; + + my $field_broken = INJECTION_BROKEN_FIELD->{$self->field} + || INJECTION_BROKEN_FIELD->{$self->field_object->type}; + + # We don't want to auto-vivify $field_broken and thus make it true. + my @operator_ok = $field_broken ? @{$field_broken->{operator_ok} || []} : (); + $field_broken = undef if grep { $_ eq $self->operator } @operator_ok; + + return $operator_broken || $field_broken || {}; } sub sql_error_ok { return $_[0]->_known_broken->{sql_error} } # Injection tests only skip fields on certain dbs. sub field_not_yet_implemented { - my ($self) = @_; - # We use the constant directly because we don't want operator_ok - # or field_ok to stop us. - my $broken = INJECTION_BROKEN_FIELD->{$self->field} - || INJECTION_BROKEN_FIELD->{$self->field_object->type}; - my $skip_for_dbs = $broken->{db_skip}; - return undef if !$skip_for_dbs; - my $dbh = Bugzilla->dbh; - if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) { - my $field = $self->field; - return "$field injection testing is not supported with $skip"; - } - return undef; + my ($self) = @_; + + # We use the constant directly because we don't want operator_ok + # or field_ok to stop us. + my $broken = INJECTION_BROKEN_FIELD->{$self->field} + || INJECTION_BROKEN_FIELD->{$self->field_object->type}; + my $skip_for_dbs = $broken->{db_skip}; + return undef if !$skip_for_dbs; + my $dbh = Bugzilla->dbh; + if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) { + my $field = $self->field; + return "$field injection testing is not supported with $skip"; + } + return undef; } + # Injection tests don't do translation. sub translated_value { $_[0]->test_value } sub name { return "injection-" . $_[0]->SUPER::name; } # Injection tests don't check content. -sub _test_content {} +sub _test_content { } sub _test_sql { - my $self = shift; - my ($sql) = @_; - my $dbh = Bugzilla->dbh; - my $name = $self->name; - if (my $error_ok = $self->sql_error_ok) { - throws_ok { $dbh->selectall_arrayref($sql) } $error_ok, - "$name: SQL query dies, as we expect"; - return; - } - return $self->SUPER::_test_sql(@_); + my $self = shift; + my ($sql) = @_; + my $dbh = Bugzilla->dbh; + my $name = $self->name; + if (my $error_ok = $self->sql_error_ok) { + throws_ok { $dbh->selectall_arrayref($sql) } $error_ok, + "$name: SQL query dies, as we expect"; + return; + } + return $self->SUPER::_test_sql(@_); } 1; diff --git a/xt/lib/Bugzilla/Test/Search/NotTest.pm b/xt/lib/Bugzilla/Test/Search/NotTest.pm index 190b8567b..ea0ecc5b2 100644 --- a/xt/lib/Bugzilla/Test/Search/NotTest.pm +++ b/xt/lib/Bugzilla/Test/Search/NotTest.pm @@ -20,9 +20,9 @@ use Bugzilla::Test::Search::Constants; # We just clone a FieldTest because that's the best for performance, # overall--that way we don't have to translate the value again. sub new { - my ($class, $field_test) = @_; - my $self = { %$field_test }; - return bless $self, $class; + my ($class, $field_test) = @_; + my $self = {%$field_test}; + return bless $self, $class; } ############# @@ -30,32 +30,33 @@ sub new { ############# sub name { - my ($self) = @_; - return "NOT(" . $self->SUPER::name . ")"; + my ($self) = @_; + return "NOT(" . $self->SUPER::name . ")"; } # True if this test is supposed to contain the numbered bug. Reversed for # NOT tests. sub bug_is_contained { - my $self = shift; - my ($number) = @_; - # No search ever returns bug 6, because it's protected by security groups - # that the searcher isn't a member of. - return 0 if $number == 6; - return $self->SUPER::bug_is_contained(@_) ? 0 : 1; + my $self = shift; + my ($number) = @_; + + # No search ever returns bug 6, because it's protected by security groups + # that the searcher isn't a member of. + return 0 if $number == 6; + return $self->SUPER::bug_is_contained(@_) ? 0 : 1; } # NOT tests have their own constant for tracking broken-ness. sub _known_broken { - my ($self) = @_; - return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check'); + my ($self) = @_; + return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check'); } sub search_params { - my ($self) = @_; - my %params = %{ $self->SUPER::search_params() }; - $params{negate0} = 1; - return \%params; + my ($self) = @_; + my %params = %{$self->SUPER::search_params()}; + $params{negate0} = 1; + return \%params; } 1; diff --git a/xt/lib/Bugzilla/Test/Search/OperatorTest.pm b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm index 5ab502dfc..57d058ad7 100644 --- a/xt/lib/Bugzilla/Test/Search/OperatorTest.pm +++ b/xt/lib/Bugzilla/Test/Search/OperatorTest.pm @@ -24,10 +24,10 @@ use Bugzilla::Test::Search::NotTest; ############### sub new { - my ($invocant, $operator, $search_test) = @_; - $search_test ||= $invocant->search_test; - my $class = ref($invocant) || $invocant; - return bless { search_test => $search_test, operator => $operator }, $class; + my ($invocant, $operator, $search_test) = @_; + $search_test ||= $invocant->search_test; + my $class = ref($invocant) || $invocant; + return bless {search_test => $search_test, operator => $operator}, $class; } ############# @@ -36,68 +36,69 @@ sub new { # The Bugzilla::Test::Search object that this is a child of. sub search_test { return $_[0]->{search_test} } + # The operator being tested sub operator { return $_[0]->{operator} } + # The tests that we're going to run on this operator. -sub tests { return @{ TESTS->{$_[0]->operator } } } +sub tests { return @{TESTS->{$_[0]->operator}} } + # The fields we're going to test for this operator. sub test_fields { return $_[0]->search_test->all_fields } sub run { - my ($self) = @_; - - foreach my $field ($self->test_fields) { - foreach my $test ($self->tests) { - my $field_test = - new Bugzilla::Test::Search::FieldTest($self, $field, $test); - $field_test->run(); - my $normal_test = - new Bugzilla::Test::Search::FieldTestNormal($field_test); - $normal_test->run(); - my $not_test = new Bugzilla::Test::Search::NotTest($field_test); - $not_test->run(); - - next if !$self->search_test->option('long'); - - # Run the OR tests. This tests every other operator (including - # this operator itself) in combination with every other field, - # in an OR with this operator and field. - foreach my $other_operator ($self->search_test->all_operators) { - $self->run_join_tests($field_test, $other_operator); - } - } - foreach my $test (INJECTION_TESTS) { - my $injection_test = - new Bugzilla::Test::Search::InjectionTest($self, $field, $test); - $injection_test->run(); - } + my ($self) = @_; + + foreach my $field ($self->test_fields) { + foreach my $test ($self->tests) { + my $field_test = new Bugzilla::Test::Search::FieldTest($self, $field, $test); + $field_test->run(); + my $normal_test = new Bugzilla::Test::Search::FieldTestNormal($field_test); + $normal_test->run(); + my $not_test = new Bugzilla::Test::Search::NotTest($field_test); + $not_test->run(); + + next if !$self->search_test->option('long'); + + # Run the OR tests. This tests every other operator (including + # this operator itself) in combination with every other field, + # in an OR with this operator and field. + foreach my $other_operator ($self->search_test->all_operators) { + $self->run_join_tests($field_test, $other_operator); + } } + foreach my $test (INJECTION_TESTS) { + my $injection_test + = new Bugzilla::Test::Search::InjectionTest($self, $field, $test); + $injection_test->run(); + } + } } sub run_join_tests { - my ($self, $field_test, $other_operator) = @_; - - my $other_operator_test = $self->new($other_operator); - foreach my $other_test ($other_operator_test->tests) { - foreach my $other_field ($self->test_fields) { - $self->_run_one_join_test($field_test, $other_operator_test, - $other_field, $other_test); - $self->search_test->clean_test_history(); - } + my ($self, $field_test, $other_operator) = @_; + + my $other_operator_test = $self->new($other_operator); + foreach my $other_test ($other_operator_test->tests) { + foreach my $other_field ($self->test_fields) { + $self->_run_one_join_test($field_test, $other_operator_test, $other_field, + $other_test); + $self->search_test->clean_test_history(); } + } } sub _run_one_join_test { - my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_; - my $other_field_test = - new Bugzilla::Test::Search::FieldTest($other_operator_test, - $other_field, $other_test); - my $or_test = new Bugzilla::Test::Search::OrTest($field_test, - $other_field_test); - $or_test->run(); - my $and_test = new Bugzilla::Test::Search::AndTest($field_test, - $other_field_test); - $and_test->run(); + my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_; + my $other_field_test + = new Bugzilla::Test::Search::FieldTest($other_operator_test, $other_field, + $other_test); + my $or_test + = new Bugzilla::Test::Search::OrTest($field_test, $other_field_test); + $or_test->run(); + my $and_test + = new Bugzilla::Test::Search::AndTest($field_test, $other_field_test); + $and_test->run(); } 1; diff --git a/xt/lib/Bugzilla/Test/Search/OrTest.pm b/xt/lib/Bugzilla/Test/Search/OrTest.pm index 1b948f38d..ebb16089d 100644 --- a/xt/lib/Bugzilla/Test/Search/OrTest.pm +++ b/xt/lib/Bugzilla/Test/Search/OrTest.pm @@ -20,36 +20,36 @@ use constant type => 'OR'; ############### sub new { - my $class = shift; - my $self = { field_tests => [@_] }; - return bless $self, $class; + my $class = shift; + my $self = {field_tests => [@_]}; + return bless $self, $class; } ############# # Accessors # ############# -sub field_tests { return @{ $_[0]->{field_tests} } } +sub field_tests { return @{$_[0]->{field_tests}} } sub search_test { ($_[0]->field_tests)[0]->search_test } sub name { - my ($self) = @_; - my @names = map { $_->name } $self->field_tests; - return join('-' . $self->type . '-', @names); + my ($self) = @_; + my @names = map { $_->name } $self->field_tests; + return join('-' . $self->type . '-', @names); } # In an OR test, bugs ARE supposed to be contained if they are contained # by ANY test. sub bug_is_contained { - my ($self, $number) = @_; - return any { $_->bug_is_contained($number) } $self->field_tests; + my ($self, $number) = @_; + return any { $_->bug_is_contained($number) } $self->field_tests; } # Needed only for failure messages sub debug_value { - my ($self) = @_; - my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests; - return join(' ' . $self->type . ' ', @values); + my ($self) = @_; + my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests; + return join(' ' . $self->type . ' ', @values); } ######################## @@ -57,61 +57,66 @@ sub debug_value { ######################## sub field_not_yet_implemented { - my ($self) = @_; - return $self->_join_messages('field_not_yet_implemented'); + my ($self) = @_; + return $self->_join_messages('field_not_yet_implemented'); } + sub invalid_field_operator_combination { - my ($self) = @_; - return $self->_join_messages('invalid_field_operator_combination'); + my ($self) = @_; + return $self->_join_messages('invalid_field_operator_combination'); } + sub search_known_broken { - my ($self) = @_; - return $self->_join_messages('search_known_broken'); + my ($self) = @_; + return $self->_join_messages('search_known_broken'); } sub _join_messages { - my ($self, $message_method) = @_; - my @messages = map { $_->$message_method } $self->field_tests; - @messages = grep { $_ } @messages; - return join(' AND ', @messages); + my ($self, $message_method) = @_; + my @messages = map { $_->$message_method } $self->field_tests; + @messages = grep {$_} @messages; + return join(' AND ', @messages); } sub _bug_will_actually_be_contained { - my ($self, $number) = @_; - - foreach my $test ($self->field_tests) { - # Some tests are broken in such a way that they actually - # generate no criteria in the SQL. In this case, the only way - # the test contains the bug is if *another* test contains it. - next if $test->_known_broken->{no_criteria}; - return 1 if $test->will_actually_contain_bug($number); - } - return 0; + my ($self, $number) = @_; + + foreach my $test ($self->field_tests) { + + # Some tests are broken in such a way that they actually + # generate no criteria in the SQL. In this case, the only way + # the test contains the bug is if *another* test contains it. + next if $test->_known_broken->{no_criteria}; + return 1 if $test->will_actually_contain_bug($number); + } + return 0; } sub contains_known_broken { - my ($self, $number) = @_; - - if ( ( $self->bug_is_contained($number) - and !$self->_bug_will_actually_be_contained($number) ) - or ( !$self->bug_is_contained($number) - and $self->_bug_will_actually_be_contained($number) ) ) - { - my @messages = map { $_->contains_known_broken($number) } - $self->field_tests; - @messages = grep { $_ } @messages; - # Sometimes, with things that break because of no_criteria, there won't - # be anything in @messages even though we need to print out a message. - if (!@messages) { - my @no_criteria = grep { $_->_known_broken->{no_criteria} } - $self->field_tests; - @messages = map { "No criteria generated by " . $_->name } - @no_criteria; - } - die "broken test with no message" if !@messages; - return join(' AND ', @messages); + my ($self, $number) = @_; + + if ( + ( + $self->bug_is_contained($number) + and !$self->_bug_will_actually_be_contained($number) + ) + or ( !$self->bug_is_contained($number) + and $self->_bug_will_actually_be_contained($number)) + ) + { + my @messages = map { $_->contains_known_broken($number) } $self->field_tests; + @messages = grep {$_} @messages; + + # Sometimes, with things that break because of no_criteria, there won't + # be anything in @messages even though we need to print out a message. + if (!@messages) { + my @no_criteria = grep { $_->_known_broken->{no_criteria} } $self->field_tests; + @messages = map { "No criteria generated by " . $_->name } @no_criteria; } - return undef; + die "broken test with no message" if !@messages; + return join(' AND ', @messages); + } + return undef; } ############################## @@ -119,23 +124,23 @@ sub contains_known_broken { ############################## sub search_columns { - my ($self) = @_; - my @columns = map { @{ $_->search_columns } } $self->field_tests; - return [uniq @columns]; + my ($self) = @_; + my @columns = map { @{$_->search_columns} } $self->field_tests; + return [uniq @columns]; } sub search_params { - my ($self) = @_; - my @all_params = map { $_->search_params } $self->field_tests; - my %params; - my $chart = 0; - foreach my $item (@all_params) { - $params{"field0-0-$chart"} = $item->{'field0-0-0'}; - $params{"type0-0-$chart"} = $item->{'type0-0-0'}; - $params{"value0-0-$chart"} = $item->{'value0-0-0'}; - $chart++; - } - return \%params; + my ($self) = @_; + my @all_params = map { $_->search_params } $self->field_tests; + my %params; + my $chart = 0; + foreach my $item (@all_params) { + $params{"field0-0-$chart"} = $item->{'field0-0-0'}; + $params{"type0-0-$chart"} = $item->{'type0-0-0'}; + $params{"value0-0-$chart"} = $item->{'value0-0-0'}; + $chart++; + } + return \%params; } 1; |