From 87ea46f7fa2b269f065181f7765352184bb59717 Mon Sep 17 00:00:00 2001 From: Max Kanat-Alexander Date: Wed, 7 Jul 2010 14:34:25 -0700 Subject: Bug 574879: Create a test that assures the correctness of Search.pm's boolean charts r=glob, a=mkanat --- xt/lib/Bugzilla/Test/Search/Constants.pm | 1011 ++++++++++++++++++++++++++++++ 1 file changed, 1011 insertions(+) create mode 100644 xt/lib/Bugzilla/Test/Search/Constants.pm (limited to 'xt/lib/Bugzilla/Test/Search/Constants.pm') diff --git a/xt/lib/Bugzilla/Test/Search/Constants.pm b/xt/lib/Bugzilla/Test/Search/Constants.pm new file mode 100644 index 000000000..95bba8ed4 --- /dev/null +++ b/xt/lib/Bugzilla/Test/Search/Constants.pm @@ -0,0 +1,1011 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Everything Solved, Inc. +# Portions created by the Initial Developer are Copyright (C) 2010 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander + + +# These are constants used by Bugzilla::Test::Search. +# See the comment at the top of that package for a general overview +# of how the search test works, and how the constants are used. +# More detailed information on each constant is available in the comments +# in this file. +package Bugzilla::Test::Search::Constants; +use base qw(Exporter); +use Bugzilla::Constants; + +our @EXPORT = qw( + ATTACHMENT_FIELDS + COLUMN_TRANSLATION + COMMENT_FIELDS + CUSTOM_FIELDS + FIELD_SIZE + FIELD_SUBSTR_SIZE + FLAG_FIELDS + INJECTION_BROKEN_FIELD + INJECTION_BROKEN_OPERATOR + INJECTION_TESTS + KNOWN_BROKEN + NUM_BUGS + NUM_SEARCH_TESTS + OR_BROKEN + OR_SKIP + SKIP_FIELDS + SUBSTR_SIZE + TESTS + TESTS_PER_RUN + USER_FIELDS +); + +# Bug 1 is designed to be found by all the "equals" tests. It has +# multiple values for several fields where other fields only have +# one value. +# +# Bug 2 and 3 have a dependency relationship with Bug 1, +# but show up in "not equals" tests. We do use bug 2 in multiple-value +# tests. +# +# Bug 4 should never show up in any equals test, and has no relationship +# with any other bug. However, it does have all its fields set. +# +# Bug 5 only has values set for mandatory fields, to expose problems +# that happen with "not equals" tests failing to catch bugs that don't +# have a value set at all. +# +# Bug 6 is a clone of Bug 1, but is in a group that the searcher isn't +# in. +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; + +# This is how many random characters we generate for most fields' names. +# (Some fields can't be this long, though, so they have custom lengths +# in Bugzilla::Test::Search). +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', +}; + +# This translates fielddefs names into Search column names. +use constant COLUMN_TRANSLATION => { + 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', + work_time => 'work_time', + commenter => 'author', + 'longdescs.isprivate' => 'is_private', +}; + +# Same as above, for Bugzilla::Attachment. +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', +}; + +# These are fields that we don't test. Test::More will mark these +# "TODO & SKIP", and not run tests for them at all. +# +# attachments.isurl can't easily be supported by us, but it's basically +# identical to isprivate and isobsolete for searching, so that's not a big +# loss. +# +# We don't support days_elapsed or owner_idle_time yet. +use constant SKIP_FIELDS => qw( + attachments.isurl + owner_idle_time + days_elapsed +); + +# During OR tests, we skip these fields. They basically just don't work +# right in OR tests, and it's too much work to document the exact tests +# that they cause to fail. +use constant OR_SKIP => qw( + percentage_complete + flagtypes.name +); + +# All the fields that represent users. +use constant USER_FIELDS => qw( + assigned_to + reporter + qa_contact + commenter + attachments.submitter + setters.login_name + requestees.login_name cc +); + +# For the "substr"-type searches, how short of a substring should +# we use? +use constant SUBSTR_SIZE => 20; +# However, for some fields, we use a different size. +use constant FIELD_SUBSTR_SIZE => { + alias => 12, + bug_file_loc => 30, + # Just the month and day. + deadline => -5, + creation_ts => -8, + delta_ts => -8, + work_time => 3, + remaining_time => 3, + see_also => 30, + target_milestone => 12, +}; + +################ +# Known Broken # +################ + +# See the KNOWN_BROKEN constant for a general description of these +# "_BROKEN" constants. + +# Search.pm currently enforces "this must be a 0 or 1" in situations +# where it should not, with two of the attachment booleans. +use constant ATTACHMENT_BOOLEANS_SEARCH_BROKEN => ( + 'attachments.ispatch' => { search => 1 }, + 'attachments.isobsolete' => { search => 1 }, +); + +# Sometimes the search for attachment booleans works, but then contains +# the wrong results, because it does not contain bugs that fully lack +# attachments. +use constant ATTACHMENT_BOOLEANS_CONTAINS_BROKEN => ( + 'attachments.isobsolete' => { contains => [5] }, + 'attachments.ispatch' => { contains => [5] }, + 'attachments.isprivate' => { contains => [5] }, +); + +# Certain fields fail all the "negative" search tests: +# +# Blocked and Dependson "notequals" only finds bugs that have +# values for the field, but where the dependency list doesn't contain +# the bug you listed. It doesn't find bugs that fully lack values for +# the fields, as it should. +# +# cc "not" matches if any CC'ed user matches, and it fails to match +# if there are no CCs on the bug. +# +# bug_group notequals doesn't find bugs that fully lack groups, +# and matches if there is one group that isn't equal. +# +# bug_file_loc can be NULL, so it gets missed by the normal +# notequals search. +# +# keywords & longdescs "notequals" match if *any* of the values +# are not equal to the string provided. Also, keywords fails to match +# if there are no keywords on the bug. +# +# attachments.* notequals doesn't find bugs that lack attachments. +# +# deadline notequals does not find bugs that lack deadlines +# +# setters notequal doesn't find bugs that fully lack flags. +# (maybe this is OK?) +# +# requestees.login_name doesn't find bugs that fully lack requestees. +use constant NEGATIVE_BROKEN => ( + ATTACHMENT_BOOLEANS_CONTAINS_BROKEN, + 'attach_data.thedata' => { contains => [5] }, + 'attachments.description' => { contains => [5] }, + 'attachments.filename' => { contains => [5] }, + 'attachments.mimetype' => { contains => [5] }, + 'attachments.submitter' => { contains => [5] }, + blocked => { contains => [3,4,5] }, + bug_file_loc => { contains => [5] }, + bug_group => { contains => [1,5] }, + cc => { contains => [1,5] }, + deadline => { contains => [5] }, + dependson => { contains => [2,4,5] }, + keywords => { contains => [1,5] }, + longdesc => { contains => [1] }, + 'longdescs.isprivate' => { contains => [1] }, + percentage_complete => { contains => [1] }, + 'requestees.login_name' => { contains => [3,4,5] }, + 'setters.login_name' => { contains => [5] }, + work_time => { contains => [1] }, + # Custom fields are busted because they can be NULL. + FIELD_TYPE_FREETEXT, { contains => [5] }, + FIELD_TYPE_BUG_ID, { contains => [5] }, + FIELD_TYPE_DATETIME, { contains => [5] }, + FIELD_TYPE_TEXTAREA, { contains => [5] }, +); + +# Shared between greaterthan and greaterthaneq. +# +# As with other fields, longdescs greaterthan matches if any comment +# matches (which might be OK). +# +# Same for keywords, bug_group, and cc. Logically, all of these might +# be OK, but it makes the operation not the logical reverse of +# 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 => ( + bug_group => { contains => [1] }, + cc => { contains => [1] }, + keywords => { contains => [1] }, + longdesc => { contains => [1] }, + FIELD_TYPE_MULTI_SELECT, { contains => [1] }, +); + +# allwords and allwordssubstr have these broken tests in common. +# +# allwordssubstr work_time only matches against a single comment, +# instead of matching against all comments on a bug. Same is true +# for the other longdesc fields, cc, keywords, and bug_group. +# +# percentage_complete just drops in 0=0 for the term. +use constant ALLWORDS_BROKEN => ( + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + bug_group => { contains => [1] }, + cc => { contains => [1] }, + keywords => { contains => [1] }, + longdesc => { contains => [1] }, + work_time => { contains => [1] }, + percentage_complete => { contains => [2,3,4,5] }, +); + +# nowords and nowordssubstr have these broken tests in common. +# +# flagtypes.name doesn't match bugs without flags. +# cc, keywords, longdescs.isprivate, and bug_group actually work properly in +# terms of excluding bug 1 (since we exclude all values in the search, +# on our test), but still fail at including bug 5. +# The longdesc* and work_time fields, coincidentally, work completely +# correctly, possibly because there's only one comment on bug 5. +use constant NOWORDS_BROKEN => ( + NEGATIVE_BROKEN, + 'flagtypes.name' => { contains => [5] }, + bug_group => { contains => [5] }, + cc => { contains => [5] }, + keywords => { contains => [5] }, + longdesc => {}, + work_time => {}, + 'longdescs.isprivate' => {}, +); + +# 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 => [2,3,4,5] }, + '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] }, + work_time => { contains => [1] }, + FIELD_TYPE_MULTI_SELECT, { contains => [1] }, +); + + +# Any test listed in KNOWN_BROKEN gets marked TODO by Test::More +# (using some complex code in Bugzilla::Test::Seach::FieldTest). +# This means that if you run the test under "prove -v", these tests will +# still show up as "not ok", but the test suite results won't show them +# as a failure. +# +# This constant contains operators as keys, which point to hashes. The hashes +# have field names as keys. Each field name points to a hash describing +# how that field/operator combination is broken. The "contains" +# array specifies that that particular "contains" test is expected +# to fail. If "search" is set to 1, then we expect the creation of the +# Bugzilla::Search object to fail. +# +# To allow handling custom fields, you can also use the field type as a key +# instead of the field name. Specifying explicit field names always overrides +# specifying a field type. +# +# Sometimes the operators have multiple tests, and one of them works +# while the other fails. In this case, we have a special override for +# "operator-value", which uniquely identifies tests. +use constant KNOWN_BROKEN => { + notequals => { NEGATIVE_BROKEN }, + # percentage_complete substring matches every bug, regardless of + # its percentage_complete value. + substring => { + percentage_complete => { contains => [2,3,4,5] }, + }, + casesubstring => { + percentage_complete => { contains => [2,3,4,5] }, + }, + notsubstring => { NEGATIVE_BROKEN }, + + # Attachment noolean fields don't work with regexes, right now, + # because they throw an error that regexes are not valid booleans. + 'regexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN }, + + # percentage_complete notregexp fails to match bugs that + # fully lack hours worked. + notregexp => { + NEGATIVE_BROKEN, + percentage_complete => { contains => [5] }, + }, + 'notregexp-^1-' => { ATTACHMENT_BOOLEANS_SEARCH_BROKEN }, + + # percentage_complete doesn't match bugs with 0 hours worked or remaining. + # + # longdescs.isprivate matches if any comment matches, instead of if all + # comments match. Same for longdescs and work_time. (Commenter is probably + # also broken in this way, but all our comments come from the same user.) + # Also, the attachments ones don't find bugs that have no attachments + # at all (which might be OK?). + # + # attachments.isprivate lessthan doesn't find bugs without attachments. + lessthan => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + 'attachments.isprivate' => { contains => [5] }, + 'longdescs.isprivate' => { contains => [1] }, + percentage_complete => { contains => [5] }, + work_time => { contains => [1,2,3,4] }, + }, + # The lessthaneq tests are broken for the same reasons, but they work + # slightly differently so they have a different set of broken tests. + lessthaneq => { + ATTACHMENT_BOOLEANS_CONTAINS_BROKEN, + 'longdescs.isprivate' => { contains => [1] }, + work_time => { contains => [2,3,4] }, + }, + + greaterthan => { GREATERTHAN_BROKEN }, + + # percentage_complete is broken -- it won't match equal values. + greaterthaneq => { + GREATERTHAN_BROKEN, + percentage_complete => { contains => [2] }, + }, + + # percentage_complete just throws 0=0 into the search term, returning + # all bugs. + anyexact => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + percentage_complete => { contains => [3,4,5] }, + }, + # bug_group anywordssubstr returns all our bugs. Not sure why. + anywordssubstr => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + percentage_complete => { contains => [3,4,5] }, + bug_group => { contains => [3,4,5] }, + }, + + 'allwordssubstr-<1>' => { ALLWORDS_BROKEN }, + 'allwordssubstr-<1>,<2>' => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + percentage_complete => { contains => [1,2,3,4,5] }, + }, + # flagtypes.name does not work here, probably because they all try to + # match against a single flag. + # Same for attach_data.thedata. + 'allwords-<1>' => { + ALLWORDS_BROKEN, + 'attach_data.thedata' => { contains => [1] }, + 'flagtypes.name' => { contains => [1] }, + }, + 'allwords-<1> <2>' => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + percentage_complete => { contains => [1,2,3,4,5] }, + }, + + nowordssubstr => { NOWORDS_BROKEN }, + # attach_data.thedata doesn't match properly with any of the plain + # "words" searches. Also, bug 5 doesn't match because it lacks + # attachments. + nowords => { + NOWORDS_BROKEN, + 'attach_data.thedata' => { contains => [1,5] }, + }, + + # anywords searches don't work on decimal values. + # bug_group anywords returns all bugs. + # attach_data doesn't work (perhaps because it's the entire + # data, or some problem with the regex?). + anywords => { + ATTACHMENT_BOOLEANS_SEARCH_BROKEN, + 'attach_data.thedata' => { contains => [1] }, + bug_group => { contains => [2,3,4,5] }, + percentage_complete => { contains => [2,3,4,5] }, + work_time => { contains => [1] }, + }, + 'anywords-<1> <2>' => { + bug_group => { contains => [3,4,5] }, + percentage_complete => { contains => [3,4,5] }, + 'attach_data.thedata' => { contains => [1,2] }, + work_time => { contains => [1,2] }, + }, + + # 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] }, + creation_ts => { contains => [1,5] }, + # attachments.* finds values where the date matches exactly. + 'attachments.description' => { contains => [2] }, + 'attachments.filename' => { contains => [2] }, + 'attachments.isobsolete' => { contains => [2] }, + 'attachments.ispatch' => { contains => [2] }, + 'attachments.isprivate' => { contains => [2] }, + 'attachments.mimetype' => { contains => [2] }, + }, + 'changedafter' => { + 'attach_data.thedata' => { contains => [2,3,4] }, + classification => { contains => [2,3,4] }, + commenter => { contains => [2,3,4] }, + creation_ts => { contains => [2,3,4] }, + delta_ts => { contains => [2,3,4] }, + percentage_complete => { contains => [1,5] }, + '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 => [1] }, + dependson => { contains => [1] }, + FIELD_TYPE_BUG_ID, { contains => [1] }, + }, + # changeto doesn't find work_time changes (probably due to decimal/string + # stuff). Same for remaining_time and estimated_time. + # + # 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] }, + }, +}; + +############# +# Overrides # +############# + +# These overrides are used in the TESTS constant, below. + +# 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.isprivate' => { value => '^1' }, + cclist_accessible => { value => '^1' }, + reporter_accessible => { value => '^1' }, + everconfirmed => { value => '^1' }, + '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.0' }, + 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] }, + target_milestone => { 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_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] }, + 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] }, + # 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] }, +); + +# 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] }, +); + +# 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] }, +); + +# For anyexact and anywordssubstr +use constant ANY_OVERRIDE => ( + '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 => [] }, +); + +######### +# Tests # +######### + +# The basic format of this is a hashref, where the keys are operators, +# and each operator has an arrayref of tests that it runs. The tests +# are hashrefs, with the following possible keys: +# +# contains: This is a list of bug numbers that the search is expected +# to contain. (This is bug numbers, like 1,2,3, not the bug +# ids. For a description of each bug number, see NUM_BUGS.) +# Any bug not listed in "contains" must *not* show up in the +# search result. +# value: The value that you're searching for. There are certain special +# codes that will be replaced with bug values when the tests are +# run. In these examples below, "#" indicates a bug number: +# +# <#> - The field value for this bug. +# +# For any operator that has the string "word" in it, this is +# *all* the values for the current field from the numbered bug, +# joined by a space. +# +# If the operator has the string "substr" in it, then we +# take a substring of the value (for single-value searches) +# or we take a substring of each value and join them (for +# multi-value "word" searches). The length of the substring +# is determined by the SUBSTR_SIZE constants above.) +# +# For other operators, this just becomes the first value from +# the field for the numbered bug. +# +# So, if we were running the "equals" test and checking the +# cc field, <1> would become the login name of the first cc on +# Bug 1. If we did an "anywords" search test, it would become +# a space-separated string of the login names of all the ccs +# 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. +# +# escape: If true, we will call quotemeta() on the value immediately +# before passing it to Search.pm. +# +# transform: A function to call on any field value before inserting +# it for a <#> replacement. The transformation function +# gets all of the bug's values for the field as its arguments. +# if_equal: This allows you to override "contains" for the case where +# the transformed value (from calling the "transform" function) +# is equal to the original value. +# +# 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>' }, + ], + casesubstring => [ + { contains => [1], value => '<1>' }, + { contains => [], value => '<1>', transform => sub { lc($_[0]) }, + extra_name => 'lc', if_equal => { contains => [1] } }, + ], + notsubstring => [ + { contains => [2,3,4,5], value => '<1>' }, + ], + regexp => [ + { contains => [1], value => '<1>', escape => 1 }, + { contains => [1], value => '^1-', override => REGEX_OVERRIDE }, + ], + notregexp => [ + { contains => [2,3,4,5], value => '<1>', escape => 1 }, + { 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-' }, + 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,5] }, + cclist_accessible => { value => 1, contains => [2,3,4,5] }, + reporter_accessible => { value => 1, contains => [2,3,4,5] }, + 'longdescs.isprivate' => { value => 1, contains => [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' }, + 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>' }, + FIELD_TYPE_DATETIME, { value => '2037-03-02' }, + LESSTHAN_OVERRIDE, + } + }, + ], + lessthaneq => [ + { contains => [1], value => '<1>', + override => { + 'attachments.ispatch' => { value => 0, contains => [2,3,4,5] }, + 'attachments.isobsolete' => { value => 0, contains => [2,3,4,5] }, + 'attachments.isprivate' => { value => 0, contains => [2,3,4,5] }, + cclist_accessible => { value => 0, contains => [2,3,4,5] }, + reporter_accessible => { value => 0, contains => [2,3,4,5] }, + 'longdescs.isprivate' => { value => 0, contains => [2,3,4,5] }, + everconfirmed => { value => 0, contains => [2,3,4,5] }, + blocked => { contains => [1,2] }, + dependson => { contains => [1,3] }, + creation_ts => { contains => [1,5] }, + delta_ts => { contains => [1,5] }, + remaining_time => { contains => [1,5] }, + longdesc => { contains => [1,5] }, + work_time => { value => 1, 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.isprivate' => { value => 0, contains => [1] }, + everconfirmed => { value => 0, contains => [1] }, + 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.isprivate' => { value => 1, contains => [1] }, + everconfirmed => { value => 1, contains => [1] }, + dependson => { 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 } }, + ], + allwordssubstr => [ + { contains => [1], value => '<1>', + override => { MULTI_BOOLEAN_OVERRIDE } }, + { contains => [], value => '<1>,<2>' }, + ], + nowordssubstr => [ + { contains => [2,3,4,5], value => '<1>', + override => { + # longdescs.isprivate translates to "1 0", so no bugs should + # show up. + 'longdescs.isprivate' => { contains => [] }, + # 1.0 0.0 exludes bug 5. + # XXX However, it also shouldn't match 2, 3, or 4, because + # they contain at least one comment with 0.0 work_time. + work_time => { contains => [2,3,4] }, + } + }, + ], + anywords => [ + { contains => [1], value => '<1>', + override => { + MULTI_BOOLEAN_OVERRIDE, + work_time => { value => '1.0', contains => [1] }, + } + }, + { contains => [1,2], value => '<1> <2>', + override => { + MULTI_BOOLEAN_OVERRIDE, + dependson => { value => '<1> <3>', contains => [1,3] }, + work_time => { value => '1.0 2.0' }, + }, + }, + ], + allwords => [ + { contains => [1], value => '<1>', + override => { MULTI_BOOLEAN_OVERRIDE } }, + { contains => [], value => '<1> <2>' }, + ], + nowords => [ + { contains => [2,3,4,5], value => '<1>', + override => { + # longdescs.isprivate translates to "1 0", so no bugs should + # show up. + 'longdescs.isprivate' => { contains => [] }, + # 1.0 0.0 exludes bug 5. + # XXX However, it also shouldn't match 2, 3, or 4, because + # they contain at least one comment with 0.0 work_time. + work_time => { contains => [2,3,4] }, + } + }, + ], + + changedbefore => [ + { contains => [1], value => '<2-delta>', + override => { + CHANGED_OVERRIDE, + creation_ts => { contains => [1,5] }, + blocked => { contains => [1,2] }, + dependson => { contains => [1,3] }, + longdesc => { contains => [1,2,5] }, + } + }, + ], + changedafter => [ + { contains => [2,3,4], value => '<1-delta>', + override => { + CHANGED_OVERRIDE, + creation_ts => { contains => [2,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, + # longdesc changedfrom doesn't make any sense. + longdesc => { contains => [] }, + # Nor does creation_ts changedfrom. + creation_ts => { contains => [] }, + 'attach_data.thedata' => { 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] }, + }, + }, + ], +}; + +# Fields that do not behave as we expect, for InjectionTest. +# search => 1 means the Bugzilla::Search creation fails. +# sql_error is a regex that specifies a SQL error that's OK for us to throw. +# operator_ok overrides the "brokenness" of certain operators, so that they +# are always OK for that field/operator combination. +use constant INJECTION_BROKEN_FIELD => { + 'attachments.isobsolete' => { search => 1 }, + 'attachments.ispatch' => { search => 1 }, + owner_idle_time => { + sql_error => qr/bugs\.owner_idle_time.+where clause/, + operator_ok => [qw(changedfrom changedto greaterthan greaterthaneq + lessthan lessthaneq)] + }, + keywords => { + search => 1, + operator_ok => [qw(allwordssubstr anywordssubstr casesubstring + changedfrom changedto greaterthan greaterthaneq + lessthan lessthaneq notregexp notsubstring + nowordssubstr regexp substring)] + }, +}; + +# 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 => ['percentage_complete'] }, + changedbefore => { search => 1, field_ok => ['percentage_complete'] }, + changedby => { search => 1, field_ok => ['percentage_complete'] }, +}; + +# 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' } +); + +# This overrides KNOWN_BROKEN for OR configurations. +# It indicates that these combinations are broken in some way that they +# aren't broken when alone, because they don't return what they logically +# should when put into an OR. +use constant OR_BROKEN => { + # Multi-value fields search on individual values, so "equals" OR "notequals" + # returns nothing, when it should instead logically return everything. + 'blocked-equals' => { + 'blocked-notequals' => { contains => [1,2,3,4,5] }, + }, + 'dependson-equals' => { + 'dependson-notequals' => { contains => [1,2,3,4,5] }, + }, + 'bug_group-equals' => { + 'bug_group-notequals' => { contains => [1,2,3,4,5] }, + }, + 'cc-equals' => { + 'cc-notequals' => { contains => [1,2,3,4,5] }, + }, + 'commenter-equals' => { + 'commenter-notequals' => { contains => [1,2,3,4,5] }, + 'longdesc-notequals' => { contains => [2,3,4,5] }, + 'longdescs.isprivate-notequals' => { contains => [2,3,4,5] }, + 'work_time-notequals' => { contains => [2,3,4,5] }, + }, + 'commenter-notequals' => { + 'commenter-equals' => { contains => [1,2,3,4,5] }, + 'longdesc-equals' => { contains => [1] }, + 'longdescs.isprivate-equals' => { contains => [1] }, + 'work_time-equals' => { contains => [1] }, + }, +}; + +1; -- cgit v1.2.3-24-g4f1b