Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
bugzilla
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ivan Ivlev
bugzilla
Commits
87ea46f7
Commit
87ea46f7
authored
Jul 07, 2010
by
Max Kanat-Alexander
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bug 574879: Create a test that assures the correctness of Search.pm's
boolean charts r=glob, a=mkanat
parent
814b24fd
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
3171 additions
and
1 deletion
+3171
-1
Bugzilla.pm
Bugzilla.pm
+3
-0
Bug.pm
Bugzilla/Bug.pm
+11
-0
Constants.pm
Bugzilla/Constants.pm
+4
-0
Error.pm
Bugzilla/Error.pm
+7
-0
Install.pm
Bugzilla/Install.pm
+2
-0
Filesystem.pm
Bugzilla/Install/Filesystem.pm
+4
-0
Search.pm
Bugzilla/Search.pm
+1
-1
README
xt/README
+18
-0
Search.pm
xt/lib/Bugzilla/Test/Search.pm
+941
-0
AndTest.pm
xt/lib/Bugzilla/Test/Search/AndTest.pm
+70
-0
Constants.pm
xt/lib/Bugzilla/Test/Search/Constants.pm
+1011
-0
FakeCGI.pm
xt/lib/Bugzilla/Test/Search/FakeCGI.pm
+62
-0
FieldTest.pm
xt/lib/Bugzilla/Test/Search/FieldTest.pm
+565
-0
InjectionTest.pm
xt/lib/Bugzilla/Test/Search/InjectionTest.pm
+78
-0
OperatorTest.pm
xt/lib/Bugzilla/Test/Search/OperatorTest.pm
+111
-0
OrTest.pm
xt/lib/Bugzilla/Test/Search/OrTest.pm
+187
-0
search.t
xt/search.t
+96
-0
No files found.
Bugzilla.pm
View file @
87ea46f7
...
...
@@ -463,6 +463,9 @@ sub usage_mode {
elsif
(
$newval
==
USAGE_MODE_EMAIL
)
{
$class
->
error_mode
(
ERROR_MODE_DIE
);
}
elsif
(
$newval
==
USAGE_MODE_TEST
)
{
$class
->
error_mode
(
ERROR_MODE_TEST
);
}
else
{
ThrowCodeError
(
'usage_mode_invalid'
,
{
'invalid_usage_mode'
,
$newval
});
...
...
Bugzilla/Bug.pm
View file @
87ea46f7
...
...
@@ -3203,6 +3203,17 @@ sub comments {
return
\
@comments
;
}
# This is needed by xt/search.t.
sub
percentage_complete
{
my
$self
=
shift
;
return
undef
if
$self
->
{
'error'
}
||
!
Bugzilla
->
user
->
is_timetracker
;
my
$remaining
=
$self
->
remaining_time
;
my
$actual
=
$self
->
actual_time
;
my
$total
=
$remaining
+
$actual
;
return
undef
if
$total
==
0
;
return
100
*
(
$actual
/
$total
);
}
sub
product
{
my
(
$self
)
=
@_
;
return
$self
->
{
product
}
if
exists
$self
->
{
product
};
...
...
Bugzilla/Constants.pm
View file @
87ea46f7
...
...
@@ -141,11 +141,13 @@ use File::Basename;
USAGE_MODE_XMLRPC
USAGE_MODE_EMAIL
USAGE_MODE_JSON
USAGE_MODE_TEST
ERROR_MODE_WEBPAGE
ERROR_MODE_DIE
ERROR_MODE_DIE_SOAP_FAULT
ERROR_MODE_JSON_RPC
ERROR_MODE_TEST
COLOR_ERROR
...
...
@@ -457,6 +459,7 @@ use constant USAGE_MODE_CMDLINE => 1;
use
constant
USAGE_MODE_XMLRPC
=>
2
;
use
constant
USAGE_MODE_EMAIL
=>
3
;
use
constant
USAGE_MODE_JSON
=>
4
;
use
constant
USAGE_MODE_TEST
=>
5
;
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
# usually). Use with Bugzilla->error_mode.
...
...
@@ -464,6 +467,7 @@ use constant ERROR_MODE_WEBPAGE => 0;
use
constant
ERROR_MODE_DIE
=>
1
;
use
constant
ERROR_MODE_DIE_SOAP_FAULT
=>
2
;
use
constant
ERROR_MODE_JSON_RPC
=>
3
;
use
constant
ERROR_MODE_TEST
=>
4
;
# The ANSI colors of messages that command-line scripts use
use
constant
COLOR_ERROR
=>
'red'
;
...
...
Bugzilla/Error.pm
View file @
87ea46f7
...
...
@@ -33,6 +33,7 @@ use Bugzilla::WebService::Constants;
use
Bugzilla::
Util
;
use
Carp
;
use
Data::
Dumper
;
use
Date::
Format
;
# We cannot use $^S to detect if we are in an eval(), because mod_perl
...
...
@@ -102,6 +103,12 @@ sub _throw_error {
$template
->
process
(
$name
,
$vars
)
||
ThrowTemplateError
(
$template
->
error
());
}
# There are some tests that throw and catch a lot of errors,
# and calling $template->process over and over for those errors
# is too slow. So instead, we just "die" with a dump of the arguments.
elsif
(
Bugzilla
->
error_mode
==
ERROR_MODE_TEST
)
{
die
Dumper
(
$vars
);
}
else
{
my
$message
;
$template
->
process
(
$name
,
$vars
,
\
$message
)
...
...
Bugzilla/Install.pm
View file @
87ea46f7
...
...
@@ -358,7 +358,9 @@ sub make_admin {
write_params
();
}
if
(
Bugzilla
->
usage_mode
==
USAGE_MODE_CMDLINE
)
{
print
"\n"
,
get_text
(
'install_admin_created'
,
{
user
=>
$user
}),
"\n"
;
}
}
sub
_prompt_for_password
{
...
...
Bugzilla/Install/Filesystem.pm
View file @
87ea46f7
...
...
@@ -241,6 +241,8 @@ sub FILESYSTEM {
dirs
=>
DIR_OWNER_WRITE
},
t
=>
{
files
=>
OWNER_WRITE
,
dirs
=>
DIR_OWNER_WRITE
},
xt
=>
{
files
=>
OWNER_WRITE
,
dirs
=>
DIR_OWNER_WRITE
},
'docs/lib'
=>
{
files
=>
OWNER_WRITE
,
dirs
=>
DIR_OWNER_WRITE
},
'docs/*/xml'
=>
{
files
=>
OWNER_WRITE
,
...
...
@@ -333,6 +335,8 @@ EOT
contents
=>
HT_DEFAULT_DENY
},
't/.htaccess'
=>
{
perms
=>
WS_SERVE
,
contents
=>
HT_DEFAULT_DENY
},
'xt/.htaccess'
=>
{
perms
=>
WS_SERVE
,
contents
=>
HT_DEFAULT_DENY
},
"$datadir/.htaccess"
=>
{
perms
=>
WS_SERVE
,
contents
=>
HT_DEFAULT_DENY
},
...
...
Bugzilla/Search.pm
View file @
87ea46f7
...
...
@@ -2159,7 +2159,7 @@ sub _owner_idle_time_greater_less {
my
$table
=
"idle_"
.
$$chartid
;
$$v
=~
/^(\d+)\s*([hHdDwWmMyY])?$/
;
my
$quantity
=
$1
;
my
$quantity
=
$1
||
0
;
my
$unit
=
lc
$2
;
my
$unitinterval
=
'DAY'
;
if
(
$unit
eq
'h'
)
{
...
...
xt/README
0 → 100644
View file @
87ea46f7
The tests in this directory require a working database, as opposed
to the tests in t/, which simply test the code without a working
installation.
Some of the tests may modify your current working installation, even
if only temporarily. To run the tests that modify your database,
set the environment variable BZ_WRITE_TESTS to 1.
Some tests also take additional, optional arguments. You can pass arguments
to tests like:
prove xt/search.t :: --long --operators=equals,notequals
Note the "::"--that is necessary to note that the arguments are going to
the test, not to "prove".
See the perldoc of the individual tests to see what options they support,
or do "perl xt/search.t --help".
xt/lib/Bugzilla/Test/Search.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This module tests Bugzilla/Search.pm. It uses various constants
# that are in Bugzilla::Test::Search::Constants, in xt/lib/.
#
# It does this by:
# 1) Creating a bunch of field values. Each field value is
# randomly named and fully unique.
# 2) Creating a bunch of bugs that use those unique field
# values. Each bug has different characteristics--see
# the comment above the NUM_BUGS constant for a description
# of each bug.
# 3) Running searches using the combination of every search operator against
# every field. The tests that we run are described by the TESTS constant.
# Some of the operator/field combinations are known to be broken--
# these are listed in the KNOWN_BROKEN constant.
# 4) For each search, we make sure that certain bugs are contained in
# the search, and certain other bugs are not contained in the search.
# The code for the operator/field tests is mostly in
# Bugzilla::Test::Search::FieldTest.
# 5) After testing each operator/field combination's functionality, we
# do additional tests to make sure that there are no SQL injections
# possible via any operator/field combination. The code for the
# SQL Injection tests is in Bugzilla::Test::Search::InjectionTest.
#
# Generally, the only way that you should modify the behavior of this
# script is by modifying the constants.
package
Bugzilla::Test::
Search
;
use
strict
;
use
warnings
;
use
Bugzilla::
Attachment
;
use
Bugzilla::
Bug
();
use
Bugzilla::
Constants
;
use
Bugzilla::
Field
;
use
Bugzilla::Field::
Choice
;
use
Bugzilla::
FlagType
;
use
Bugzilla::
Group
;
use
Bugzilla::
Install
();
use
Bugzilla::Test::Search::
Constants
;
use
Bugzilla::Test::Search::
OperatorTest
;
use
Bugzilla::
User
();
use
Bugzilla::
Util
qw(generate_random_password)
;
use
Carp
;
use
DateTime
;
use
Scalar::
Util
qw(blessed)
;
###############
# Constructor #
###############
sub
new
{
my
(
$class
,
$options
)
=
@_
;
return
bless
{
options
=>
$options
},
$class
;
}
#############
# Accessors #
#############
sub
options
{
return
$_
[
0
]
->
{
options
}
}
sub
option
{
return
$_
[
0
]
->
{
options
}
->
{
$_
[
1
]}
}
sub
num_tests
{
my
(
$self
)
=
@_
;
my
@top_operators
=
$self
->
top_level_operators
;
my
@all_operators
=
$self
->
all_operators
;
my
$top_operator_tests
=
$self
->
_total_operator_tests
(
\
@top_operators
);
my
$all_operator_tests
=
$self
->
_total_operator_tests
(
\
@all_operators
);
my
@fields
=
$self
->
all_fields
;
# Basically, we run TESTS_PER_RUN tests for each field/operator combination.
my
$top_combinations
=
$top_operator_tests
*
scalar
(
@fields
);
my
$all_combinations
=
$all_operator_tests
*
scalar
(
@fields
);
# But we also have ORs, for which we run combinations^2 tests.
my
$join_tests
=
$self
->
option
(
'long'
)
?
(
$top_combinations
*
$all_combinations
)
:
0
;
# And AND tests, which means we run 2x $join_tests;
$join_tests
=
$join_tests
*
2
;
my
$operator_field_tests
=
(
$top_combinations
+
$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
;
return
$operator_field_tests
+
$sql_injection_tests
;
}
sub
_total_operator_tests
{
my
(
$self
,
$operators
)
=
@_
;
# Some operators have more than one test. Find those ones and add
# them to the total operator tests
my
$extra_operator_tests
;
foreach
my
$operator
(
@$operators
)
{
my
$tests
=
TESTS
->
{
$operator
};
next
if
!
$tests
;
my
$extra_num
=
scalar
(
@$tests
)
-
1
;
$extra_operator_tests
+=
$extra_num
;
}
return
scalar
(
@$operators
)
+
$extra_operator_tests
;
}
sub
all_operators
{
my
(
$self
)
=
@_
;
if
(
not
$self
->
{
all_operators
})
{
my
@operators
;
if
(
my
$limit_operators
=
$self
->
option
(
'operators'
))
{
@operators
=
split
(
','
,
$limit_operators
);
}
else
{
@operators
=
sort
(
keys
%
{
Bugzilla::Search::
OPERATORS
()
});
}
# "substr" is just a backwards-compatibility operator, same as "substring".
@operators
=
grep
{
$_
ne
'substr'
}
@operators
;
$self
->
{
all_operators
}
=
\
@operators
;
}
return
@
{
$self
->
{
all_operators
}
};
}
sub
all_fields
{
my
$self
=
shift
;
if
(
not
$self
->
{
all_fields
})
{
$self
->
_create_custom_fields
();
my
@fields
=
Bugzilla
->
get_fields
;
@fields
=
sort
{
$a
->
name
cmp
$b
->
name
}
@fields
;
$self
->
{
all_fields
}
=
\
@fields
;
}
return
@
{
$self
->
{
all_fields
}
};
}
sub
top_level_operators
{
my
(
$self
)
=
@_
;
if
(
!
$self
->
{
top_level_operators
})
{
my
@operators
;
my
$limit_top
=
$self
->
option
(
'top-operators'
);
if
(
$limit_top
)
{
@operators
=
split
(
','
,
$limit_top
);
}
else
{
@operators
=
$self
->
all_operators
;
}
$self
->
{
top_level_operators
}
=
\
@operators
;
}
return
@
{
$self
->
{
top_level_operators
}
};
}
sub
text_fields
{
my
(
$self
)
=
@_
;
my
@text_fields
=
grep
{
$_
->
type
==
FIELD_TYPE_TEXTAREA
or
$_
->
type
==
FIELD_TYPE_FREETEXT
}
$self
->
all_fields
;
@text_fields
=
map
{
$_
->
name
}
@text_fields
;
push
(
@text_fields
,
qw(short_desc status_whiteboard bug_file_loc see_also)
);
return
@text_fields
;
}
sub
bugs
{
my
$self
=
shift
;
$self
->
{
bugs
}
||=
[
map
{
$self
->
_create_one_bug
(
$_
)
}
(
1
..
NUM_BUGS
)];
return
@
{
$self
->
{
bugs
}
};
}
# Get a numbered bug.
sub
bug
{
my
(
$self
,
$number
)
=
@_
;
return
(
$self
->
bugs
)[
$number
-
1
];
}
sub
admin
{
my
$self
=
shift
;
if
(
!
$self
->
{
admin_user
})
{
my
$admin
=
create_user
(
"admin"
);
Bugzilla::Install::
make_admin
(
$admin
);
$self
->
{
admin_user
}
=
$admin
;
}
# We send back a fresh object every time, to make sure that group
# memberships are always up-to-date.
return
new
Bugzilla::
User
(
$self
->
{
admin_user
}
->
id
);
}
sub
nobody
{
my
$self
=
shift
;
$self
->
{
nobody
}
||=
Bugzilla::
Group
->
create
({
name
=>
"nobody-"
.
random
(),
description
=>
"Nobody"
,
isbuggroup
=>
1
});
return
$self
->
{
nobody
};
}
sub
everybody
{
my
(
$self
)
=
@_
;
$self
->
{
everybody
}
||=
create_group
(
'To The Limit'
);
return
$self
->
{
everybody
};
}
sub
bug_create_value
{
my
(
$self
,
$number
,
$field
)
=
@_
;
$field
=
$field
->
name
if
blessed
(
$field
);
if
(
$number
==
6
and
$field
ne
'alias'
)
{
$number
=
1
;
}
my
$value
=
$self
->
_bug_create_values
->
{
$number
}
->
{
$field
};
return
$value
if
defined
$value
;
return
$self
->
_extra_bug_create_values
->
{
$number
}
->
{
$field
};
}
sub
bug_update_value
{
my
(
$self
,
$number
,
$field
)
=
@_
;
$field
=
$field
->
name
if
blessed
(
$field
);
if
(
$number
==
6
and
$field
ne
'alias'
)
{
$number
=
1
;
}
return
$self
->
_bug_update_values
->
{
$number
}
->
{
$field
};
}
# Values used to create the bugs.
sub
_bug_create_values
{
my
$self
=
shift
;
return
$self
->
{
bug_create_values
}
if
$self
->
{
bug_create_values
};
my
%
values
;
foreach
my
$number
(
1
..
NUM_BUGS
)
{
$values
{
$number
}
=
$self
->
_create_field_values
(
$number
,
'for create'
);
}
$self
->
{
bug_create_values
}
=
\%
values
;
return
$self
->
{
bug_create_values
};
}
# Values as they existed on the bug, at creation time. Used by the
# changedfrom tests.
sub
_extra_bug_create_values
{
my
$self
=
shift
;
$self
->
{
extra_bug_create_values
}
||=
{
map
{
$_
=>
{}
}
(
1
..
NUM_BUGS
)
};
return
$self
->
{
extra_bug_create_values
};
}
# Values used to update the bugs after they are created.
sub
_bug_update_values
{
my
$self
=
shift
;
return
$self
->
{
bug_update_values
}
if
$self
->
{
bug_update_values
};
my
%
values
;
foreach
my
$number
(
1
..
NUM_BUGS
)
{
$values
{
$number
}
=
$self
->
_create_field_values
(
$number
);
}
$self
->
{
bug_update_values
}
=
\%
values
;
return
$self
->
{
bug_update_values
};
}
##############################
# General Helper Subroutines #
##############################
sub
random
{
$_
[
0
]
||=
FIELD_SIZE
;
generate_random_password
(
@_
);
}
# We need to use a custom timestamp for each create() and update(),
# because the database returns the same value for LOCALTIMESTAMP(0)
# for the entire transaction, and we need each created bug to have
# its own creation_ts and delta_ts.
sub
timestamp
{
my
(
$day
,
$second
)
=
@_
;
return
DateTime
->
new
(
year
=>
2037
,
month
=>
1
,
day
=>
$day
,
hour
=>
12
,
minute
=>
$second
,
second
=>
0
,
# We make it floating because the timezone doesn't matter for our uses,
# and we want totally consistent behavior across all possible machines.
time_zone
=>
'floating'
,
);
}
sub
create_keyword
{
my
(
$number
)
=
@_
;
return
Bugzilla::
Keyword
->
create
({
name
=>
"$number-keyword-"
.
random
(),
description
=>
"Keyword $number"
});
}
sub
create_user
{
my
(
$prefix
)
=
@_
;
my
$user_name
=
$prefix
.
'-'
.
random
(
10
)
.
"@"
.
random
(
10
)
.
"."
.
random
(
3
);
my
$user_realname
=
$prefix
.
'-'
.
random
();
my
$user
=
Bugzilla::
User
->
create
({
login_name
=>
$user_name
,
realname
=>
$user_realname
,
cryptpassword
=>
'*'
,
});
return
$user
;
}
sub
create_group
{
my
(
$prefix
)
=
@_
;
return
Bugzilla::
Group
->
create
({
name
=>
"$prefix-group-"
.
random
(),
description
=>
"Everybody $prefix"
,
userregexp
=>
'.*'
,
isbuggroup
=>
1
});
}
sub
create_legal_value
{
my
(
$field
,
$number
)
=
@_
;
my
$type
=
Bugzilla::Field::
Choice
->
type
(
$field
);
my
$field_name
=
$field
->
name
;
return
$type
->
create
({
value
=>
"$number-$field_name-"
.
random
(),
is_open
=>
0
});
}
#########################
# Custom Field Creation #
#########################
sub
_create_custom_fields
{
my
(
$self
)
=
@_
;
return
if
!
$self
->
option
(
'add-custom-fields'
);
while
(
my
(
$type
,
$name
)
=
each
%
{
CUSTOM_FIELDS
()
})
{
my
$exists
=
new
Bugzilla::
Field
({
name
=>
$name
});
next
if
$exists
;
Bugzilla::
Field
->
create
({
name
=>
$name
,
type
=>
$type
,
description
=>
"Search Test Field $name"
,
enter_bug
=>
1
,
custom
=>
1
,
buglist
=>
1
,
is_mandatory
=>
0
,
});
}
}
########################
# Field Value Creation #
########################
sub
_create_field_values
{
my
(
$self
,
$number
,
$for_create
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
Bugzilla
->
set_user
(
$self
->
admin
);
my
@selects
=
grep
{
$_
->
is_select
}
$self
->
all_fields
;
my
%
values
;
foreach
my
$field
(
@selects
)
{
next
if
$field
->
is_abnormal
;
$values
{
$field
->
name
}
=
create_legal_value
(
$field
,
$number
)
->
name
;
}
my
$group
=
create_group
(
$number
);
$values
{
groups
}
=
[
$group
->
name
];
$values
{
'keywords'
}
=
create_keyword
(
$number
)
->
name
;
foreach
my
$field
qw(assigned_to qa_contact reporter cc)
{
$values
{
$field
}
=
create_user
(
"$number-$field-"
)
->
login
;
}
my
$classification
=
Bugzilla::
Classification
->
create
(
{
name
=>
"$number-classification-"
.
random
()
});
$classification
=
$classification
->
name
;
my
$version
=
"$number-version-"
.
random
();
my
$milestone
=
"$number-tm-"
.
random
(
15
);
my
$product
=
Bugzilla::
Product
->
create
({
name
=>
"$number-product-"
.
random
(),
description
=>
'Created by t/search.t'
,
defaultmilestone
=>
$milestone
,
classification
=>
$classification
,
version
=>
$version
,
allows_unconfirmed
=>
1
,
});
foreach
my
$item
(
$group
,
$self
->
nobody
)
{
$product
->
set_group_controls
(
$item
,
{
membercontrol
=>
CONTROLMAPSHOWN
,
othercontrol
=>
CONTROLMAPNA
});
}
# $product->update() is called lower down.
my
$component
=
Bugzilla::
Component
->
create
({
product
=>
$product
,
name
=>
"$number-component-"
.
random
(),
initialowner
=>
create_user
(
"$number-defaultowner"
)
->
login
,
initialqacontact
=>
create_user
(
"$number-defaultqa"
)
->
login
,
initial_cc
=>
[
create_user
(
"$number-initcc"
)
->
login
],
description
=>
"Component $number"
});
$values
{
'product'
}
=
$product
->
name
;
$values
{
'component'
}
=
$component
->
name
;
$values
{
'target_milestone'
}
=
$milestone
;
$values
{
'version'
}
=
$version
;
foreach
my
$field
(
$self
->
text_fields
)
{
# We don't add a - after $field for the text fields, because
# if we do, fulltext searching for short_desc pulls out
# "short_desc" as a word and matches it in every bug.
my
$value
=
"$number-$field"
.
random
();
if
(
$field
eq
'bug_file_loc'
or
$field
eq
'see_also'
)
{
$value
=
"http://$value"
.
random
(
3
)
.
"/show_bug.cgi?id=$number"
;
}
$values
{
$field
}
=
$value
;
}
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-setter"
);
my
$requestee
=
create_user
(
"$number-requestee"
);
$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
;
}
}
$product
->
update
();
return
\%
values
;
}
# Flags
sub
_create_flags
{
my
(
$number
,
$setter
,
$requestee
)
=
@_
;
my
$flagtypes
=
_create_flagtypes
(
$number
);
my
%
flags
;
foreach
my
$type
qw(a b)
{
$flags
{
$type
}
=
_get_flag_values
(
@_
,
$flagtypes
->
{
$type
});
}
return
\%
flags
;
}
sub
_create_flagtypes
{
my
(
$number
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
my
$name
=
"$number-flag-"
.
random
();
my
$desc
=
"FlagType $number"
;
my
%
flagtypes
;
foreach
my
$target
(
qw(a b)
)
{
$dbh
->
do
(
"INSERT INTO flagtypes
(name, description, target_type, is_requestable,
is_requesteeble, is_multiplicable, cc_list)
VALUES (?,?,?,1,1,1,'')"
,
undef
,
$name
,
$desc
,
$target
);
my
$id
=
$dbh
->
bz_last_key
(
'flagtypes'
,
'id'
);
$dbh
->
do
(
'INSERT INTO flaginclusions (type_id) VALUES (?)'
,
undef
,
$id
);
my
$flagtype
=
new
Bugzilla::
FlagType
(
$id
);
$flagtypes
{
$target
}
=
$flagtype
;
}
return
\%
flagtypes
;
}
sub
_get_flag_values
{
my
(
$number
,
$setter
,
$requestee
,
$flagtype
)
=
@_
;
my
@set_flags
;
if
(
$number
<=
2
)
{
foreach
my
$value
(
qw(? - + ?)
)
{
my
$flag
=
{
type_id
=>
$flagtype
->
id
,
status
=>
$value
,
setter
=>
$setter
,
flagtype
=>
$flagtype
};
push
(
@set_flags
,
$flag
);
}
$set_flags
[
0
]
->
{
requestee
}
=
$requestee
->
login
;
}
else
{
@set_flags
=
({
type_id
=>
$flagtype
->
id
,
status
=>
'+'
,
setter
=>
$setter
,
flagtype
=>
$flagtype
});
}
return
\
@set_flags
;
}
# Attachments
sub
_get_attach_values
{
my
(
$number
,
$for_create
)
=
@_
;
my
$boolean
=
$number
==
1
?
1
:
0
;
if
(
$for_create
)
{
$boolean
=
!
$boolean
?
1
:
0
;
}
my
$ispatch
=
$for_create
?
'ispatch'
:
'is_patch'
;
my
$isobsolete
=
$for_create
?
'isobsolete'
:
'is_obsolete'
;
my
$isprivate
=
$for_create
?
'isprivate'
:
'is_private'
;
my
$mimetype
=
$for_create
?
'mimetype'
:
'content_type'
;
my
%
values
=
(
description
=>
"$number-attach_desc-"
.
random
(),
filename
=>
"$number-filename-"
.
random
(),
$ispatch
=>
$boolean
,
$isobsolete
=>
$boolean
,
$isprivate
=>
$boolean
,
$mimetype
=>
"text/x-$number-"
.
random
(),
);
if
(
$for_create
)
{
$values
{
data
}
=
"$number-data-"
.
random
()
.
random
();
}
return
\%
values
;
}
################
# Bug Creation #
################
sub
_create_one_bug
{
my
(
$self
,
$number
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
# We need bug 6 to have a unique alias that is not a clone of bug 1's,
# so we get the alias separately from the other parameters.
my
$alias
=
$self
->
bug_create_value
(
$number
,
'alias'
);
my
$update_alias
=
$self
->
bug_update_value
(
$number
,
'alias'
);
# Otherwise, make bug 6 a clone of bug 1.
$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)
};
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 flags percentage_complete
keyword_objects everconfirmed dependson blocked
groups_in)
{
$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
;
}
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) VALUES (?,?)'
,
undef
,
$bug
->
id
,
$see_also
);
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
);
}
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_add
=
$update_params
{
cc
};
$cc_add
=
[
$cc_add
]
if
!
ref
$cc_add
;
$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
);
# It's not generally safe to do update() multiple times on
# the same Bug object.
$bug
=
new
Bugzilla::
Bug
(
$bug
->
id
);
my
$update_flags
=
$self
->
bug_update_value
(
$number
,
'set_flags'
)
->
{
b
};
$_
->
{
status
}
=
'X'
foreach
@
{
$bug
->
flags
};
$bug
->
set_flags
(
$bug
->
flags
,
$update_flags
);
if
(
$number
==
1
)
{
my
$comment_id
=
$bug
->
comments
->
[
-
1
]
->
id
;
$bug
->
set_comment_is_private
({
$comment_id
=>
1
});
}
$bug
->
update
(
$bug
->
delta_ts
);
my
$attach_create
=
$self
->
bug_create_value
(
$number
,
'attachment'
);
my
$attachment
=
Bugzilla::
Attachment
->
create
({
bug
=>
$bug
,
creation_ts
=>
$creation_ts
,
%
$attach_create
});
# Store for the changedfrom tests.
$extra_values
->
{
attachments
}
=
[
new
Bugzilla::
Attachment
(
$attachment
->
id
)];
my
$attach_update
=
$self
->
bug_update_value
(
$number
,
'attachment'
);
$attachment
->
set_all
(
$attach_update
);
# In order to keep the mimetype on the ispatch attachment,
# we need to bypass the validator.
$attachment
->
{
mimetype
}
=
$attach_update
->
{
content_type
};
my
$attach_flags
=
$self
->
bug_update_value
(
$number
,
'set_flags'
)
->
{
a
};
$attachment
->
set_flags
(
[]
,
$attach_flags
);
$attachment
->
update
(
$bug
->
delta_ts
);
}
# Values for changedfrom.
$extra_values
->
{
creation_ts
}
=
$bug
->
creation_ts
;
$extra_values
->
{
delta_ts
}
=
$bug
->
creation_ts
;
return
new
Bugzilla::
Bug
(
$bug
->
id
);
}
###################################
# Test::Builder Memory Efficiency #
###################################
# Test::Builder stores information for each test run, but Test::Harness
# and TAP::Harness don't actually need this information. When we run 60
# million tests, the history eats up all our memory. (After about
# 1 million tests, memory usage is around 1 GB.)
#
# The only part of the history that Test::More actually *uses* is the "ok"
# field, which we store more efficiently, in an array, and then we re-populate
# the Test_Results in Test::Builder at the end of the test.
sub
clean_test_history
{
my
(
$self
)
=
@_
;
return
if
!
$self
->
option
(
'long'
);
my
$builder
=
Test::
More
->
builder
;
my
$current_test
=
$builder
->
current_test
;
# I don't use details() because I don't want to copy the array.
my
$results
=
$builder
->
{
Test_Results
};
my
$check_test
=
$current_test
-
1
;
while
(
my
$result
=
$results
->
[
$check_test
])
{
last
if
!
$result
;
$self
->
test_success
(
$check_test
,
$result
->
{
ok
});
$check_test
--
;
}
# Truncate the test history array, but retain the current test number.
$builder
->
{
Test_Results
}
=
[]
;
$builder
->
{
Curr_Test
}
=
$current_test
;
}
sub
test_success
{
my
(
$self
,
$index
,
$status
)
=
@_
;
$self
->
{
test_success
}
->
[
$index
]
=
$status
;
return
$self
->
{
test_success
};
}
sub
repopulate_test_results
{
my
(
$self
)
=
@_
;
return
if
!
$self
->
option
(
'long'
);
$self
->
clean_test_history
();
# We create only two hashes, for memory efficiency.
my
%
ok
=
(
ok
=>
1
);
my
%
not_ok
=
(
ok
=>
0
);
my
@results
;
foreach
my
$success
(
@
{
$self
->
{
test_success
}
})
{
push
(
@results
,
$success
?
\%
ok
:
\%
not_ok
);
}
my
$builder
=
Test::
More
->
builder
;
$builder
->
{
Test_Results
}
=
\
@results
;
}
##########
# Caches #
##########
# When doing AND and OR tests, we essentially test the same field/operator
# combinations over and over. So, if we're going to be running those tests,
# we cache the translated_value of the FieldTests globally so that we don't
# have to re-run the value-translation code every time (which can be pretty
# slow).
sub
value_translation_cache
{
my
(
$self
,
$field_test
,
$value
)
=
@_
;
return
if
!
$self
->
option
(
'long'
);
my
$test_name
=
$field_test
->
name
;
if
(
@_
==
3
)
{
$self
->
{
value_translation_cache
}
->
{
$test_name
}
=
$value
;
}
return
$self
->
{
value_translation_cache
}
->
{
$test_name
};
}
#############
# Main Test #
#############
sub
run
{
my
(
$self
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
# We want backtraces on any "die" message or any warning.
# Otherwise it's hard to trace errors inside of Bugzilla::Search from
# reading automated test run results.
local
$SIG
{
__WARN__
}
=
\&
Carp::
cluck
;
local
$SIG
{
__DIE__
}
=
\&
Carp::
confess
;
$dbh
->
bz_start_transaction
();
# Some parameters need to be set in order for the tests to function
# properly.
my
$everybody
=
$self
->
everybody
;
my
$params
=
Bugzilla
->
params
;
local
$params
->
{
'useclassification'
}
=
1
;
local
$params
->
{
'useqacontact'
}
=
1
;
local
$params
->
{
'usebugaliases'
}
=
1
;
local
$params
->
{
'usetargetmilestone'
}
=
1
;
local
$params
->
{
'mail_delivery_method'
}
=
'None'
;
local
$params
->
{
'timetrackinggroup'
}
=
$everybody
->
name
;
local
$params
->
{
'insidergroup'
}
=
$everybody
->
name
;
$self
->
_setup_bugs
();
# Even though _setup_bugs set us as an admin, we want to be sure at
# this point that we have an admin with refreshed group memberships.
Bugzilla
->
set_user
(
$self
->
admin
);
foreach
my
$operator
(
$self
->
top_level_operators
)
{
my
$operator_test
=
new
Bugzilla::Test::Search::
OperatorTest
(
$operator
,
$self
);
$operator_test
->
run
();
}
# Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
my
@bug_ids
=
map
{
$_
->
id
}
$self
->
bugs
;
my
$bug_id_string
=
join
(
','
,
@bug_ids
);
$dbh
->
do
(
"DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)"
);
$dbh
->
bz_rollback_transaction
();
$self
->
repopulate_test_results
();
}
# This makes a few changes to the bugs after they're created--changes
# that can only be done after all the bugs have been created.
sub
_setup_bugs
{
my
(
$self
)
=
@_
;
$self
->
_setup_dependencies
();
$self
->
_set_bug_id_fields
();
$self
->
_protect_bug_6
();
}
sub
_setup_dependencies
{
my
(
$self
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
# Set up depedency relationships between the bugs.
# Bug 1 + 6 depend on bug 2 and block bug 3.
my
$bug2
=
$self
->
bug
(
2
);
my
$bug3
=
$self
->
bug
(
3
);
foreach
my
$number
(
1
,
6
)
{
my
$bug
=
$self
->
bug
(
$number
);
my
@original_delta
=
(
$bug2
->
delta_ts
,
$bug3
->
delta_ts
);
Bugzilla
->
set_user
(
$bug
->
reporter
);
$bug
->
set_dependencies
([
$bug2
->
id
],
[
$bug3
->
id
]);
$bug
->
update
(
$bug
->
delta_ts
);
# Setting dependencies changed the delta_ts on bug2 and bug3, so
# re-set them back to what they were before. However, we leave
# the correct update times in bugs_activity, so that the changed*
# searches still work right.
my
$set_delta
=
$dbh
->
prepare
(
'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?'
);
foreach
my
$row
([
$original_delta
[
0
],
$bug2
->
id
],
[
$original_delta
[
1
],
$bug3
->
id
])
{
$set_delta
->
execute
(
@$row
);
}
}
}
sub
_set_bug_id_fields
{
my
(
$self
)
=
@_
;
# BUG_ID fields couldn't be set before, because before we create bug 1,
# we don't necessarily have any valid bug ids.)
my
@bug_id_fields
=
grep
{
$_
->
type
==
FIELD_TYPE_BUG_ID
}
$self
->
all_fields
;
foreach
my
$number
(
1
..
NUM_BUGS
)
{
my
$bug
=
$self
->
bug
(
$number
);
$number
=
1
if
$number
==
6
;
next
if
$number
==
5
;
my
$other_bug
=
$self
->
bug
(
$number
+
1
);
Bugzilla
->
set_user
(
$bug
->
reporter
);
foreach
my
$field
(
@bug_id_fields
)
{
$bug
->
set_custom_field
(
$field
,
$other_bug
->
id
);
$bug
->
update
(
$bug
->
delta_ts
);
}
}
}
sub
_protect_bug_6
{
my
(
$self
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
Bugzilla
->
set_user
(
$self
->
admin
);
# Put bug6 in the nobody group.
my
$nobody
=
$self
->
nobody
;
# We pull it newly from the DB to be sure it's safe to call update()
# on.
my
$bug6
=
new
Bugzilla::
Bug
(
$self
->
bug
(
6
)
->
id
);
$bug6
->
add_group
(
$nobody
);
$bug6
->
update
(
$bug6
->
delta_ts
);
# Remove the admin (and everybody else) from the $nobody group.
$dbh
->
do
(
'DELETE FROM group_group_map
WHERE grantor_id = ? OR member_id = ?'
,
undef
,
$nobody
->
id
,
$nobody
->
id
);
}
1
;
xt/lib/Bugzilla/Test/Search/AndTest.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This test combines two field/operator combinations using AND in
# a single boolean chart.
package
Bugzilla::Test::Search::
AndTest
;
use
base
qw(Bugzilla::Test::Search::OrTest)
;
use
Bugzilla::Test::Search::
Constants
;
use
Bugzilla::Test::Search::
FakeCGI
;
use
List::
MoreUtils
qw(all)
;
use
constant
type
=>
'AND'
;
#############
# Accessors #
#############
# 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
;
}
########################
# SKIP & TODO Messages #
########################
sub
_join_skip
{
()
}
sub
_join_broken_constant
{
{}
}
##############################
# Bugzilla::Search arguments #
##############################
sub
search_params
{
my
(
$self
)
=
@_
;
my
@all_params
=
map
{
$_
->
search_params
}
$self
->
field_tests
;
my
$params
=
new
Bugzilla::Test::Search::
FakeCGI
;
my
$chart
=
0
;
foreach
my
$item
(
@all_params
)
{
$params
->
param
(
"field0-$chart-0"
,
$item
->
param
(
'field0-0-0'
));
$params
->
param
(
"type0-$chart-0"
,
$item
->
param
(
'type0-0-0'
));
$params
->
param
(
"value0-$chart-0"
,
$item
->
param
(
'value0-0-0'
));
$chart
++
;
}
return
$params
;
}
1
;
\ No newline at end of file
xt/lib/Bugzilla/Test/Search/Constants.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# 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
;
xt/lib/Bugzilla/Test/Search/FakeCGI.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Calling CGI::param over and over turned out to be one of the slowest
# parts of search.t. So we create a simpler thing here that just supports
# "param" in a fast way.
package
Bugzilla::Test::Search::
FakeCGI
;
sub
new
{
my
(
$class
)
=
@_
;
return
bless
{},
$class
;
}
sub
param
{
my
(
$self
,
$name
,
@values
)
=
@_
;
if
(
!
defined
$name
)
{
return
keys
%
$self
;
}
if
(
@values
)
{
if
(
ref
$values
[
0
]
eq
'ARRAY'
)
{
$self
->
{
$name
}
=
$values
[
0
];
}
else
{
$self
->
{
$name
}
=
\
@values
;
}
}
return
()
if
!
exists
$self
->
{
$name
};
my
$item
=
$self
->
{
$name
};
return
wantarray
?
@
{
$item
||
[]
}
:
$item
->
[
0
];
}
sub
delete
{
my
(
$self
,
$name
)
=
@_
;
delete
$self
->
{
$name
};
}
# We don't need to do this, because we don't use old params in search.t.
sub
convert_old_params
{}
1
;
\ No newline at end of file
xt/lib/Bugzilla/Test/Search/FieldTest.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This module represents the tests that get run on a single
# operator/field combination for Bugzilla::Test::Search.
# This is where all the actual testing happens.
package
Bugzilla::Test::Search::
FieldTest
;
use
strict
;
use
warnings
;
use
Bugzilla::Test::Search::
FakeCGI
;
use
Bugzilla::
Search
;
use
Bugzilla::Test::Search::
Constants
;
use
Data::
Dumper
;
use
Scalar::
Util
qw(blessed)
;
use
Test::
More
;
use
Test::
Exception
;
###############
# Constructor #
###############
sub
new
{
my
(
$class
,
$operator_test
,
$field
,
$test
)
=
@_
;
return
bless
{
operator_test
=>
$operator_test
,
field_object
=>
$field
,
raw_test
=>
$test
},
$class
;
}
#############
# Accessors #
#############
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
)
=
@_
;
return
$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
(
@_
);
}
# 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
;
}
# 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
};
}
# 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
};
}
# The values for this field for the numbered bug.
sub
bug_values
{
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
}
};
# 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
;
}
return
$self
->
{
translated_value
};
}
# Used in failure diagnostic messages.
sub
debug_value
{
my
(
$self
)
=
@_
;
return
"Value: '"
.
$self
->
translated_value
.
"'"
;
}
# 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
(
defined
$value
)
{
$self
->
{
transformed_value_was_equal
}
->
{
$number
}
=
$value
;
}
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
))
{
$contains
=
$self
->
test
->
{
if_equal
}
->
{
contains
};
}
return
grep
(
$_
==
$number
,
@$contains
)
?
1
:
0
;
}
###################################################
# Accessors: Ways of doing SKIP and TODO on tests #
###################################################
# The tests we know are broken for this operator/field combination.
sub
_known_broken
{
my
$self
=
shift
;
my
$field
=
$self
->
field
;
my
$type
=
$self
->
field_object
->
type
;
my
$operator
=
$self
->
operator
;
my
$value
=
$self
->
main_value
;
my
$value_name
=
"$operator-$value"
;
my
$value_broken
=
KNOWN_BROKEN
->
{
$value_name
}
->
{
$field
};
$value_broken
||=
KNOWN_BROKEN
->
{
$value_name
}
->
{
$type
};
return
$value_broken
if
$value_broken
;
my
$operator_broken
=
KNOWN_BROKEN
->
{
$operator
}
->
{
$field
};
$operator_broken
||=
KNOWN_BROKEN
->
{
$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
$contains_broken
=
$self
->
_known_broken
->
{
contains
}
||
[]
;
if
(
grep
(
$_
==
$number
,
@$contains_broken
))
{
return
"$field $operator contains $number is known to be broken"
;
}
return
undef
;
}
# 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
;
}
# 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
;
}
# 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
;
}
# 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
};
}
}
return
$or_broken
;
}
#########################################
# Accessors: Bugzilla::Search Arguments #
#########################################
# The CGI object that will get passed to Bugzilla::Search as its arguments.
sub
search_params
{
my
$self
=
shift
;
return
$self
->
{
search_params
}
if
$self
->
{
search_params
};
my
$field
=
$self
->
field
;
my
$operator
=
$self
->
operator
;
my
$value
=
$self
->
translated_value
;
my
$cgi
=
new
Bugzilla::Test::Search::
FakeCGI
;
$cgi
->
param
(
"field0-0-0"
,
$field
);
$cgi
->
param
(
'type0-0-0'
,
$operator
);
$cgi
->
param
(
'value0-0-0'
,
$value
);
$self
->
{
search_params
}
=
$cgi
;
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
;
}
################
# Field Values #
################
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
'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'
);
}
# 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
$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
);
}
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
};
}
#####################
# Value Translation #
#####################
# This function translates the "value" specified in TESTS into an actual
# search value to pass to Search.pm. This means that we get the value
# from the current bug (or, in the case of changedfrom, from %original_values)
# 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
);
}
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
;
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
$substr_size
=
SUBSTR_SIZE
;
if
(
exists
FIELD_SUBSTR_SIZE
->
{
$field
})
{
$substr_size
=
FIELD_SUBSTR_SIZE
->
{
$field
};
}
if
(
$substr_size
>
0
)
{
return
substr
(
$value
,
0
,
$substr_size
);
}
return
substr
(
$value
,
$substr_size
);
}
#####################
# Main Test Methods #
#####################
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
();
}
}
}
sub
do_tests
{
my
(
$self
)
=
@_
;
my
$name
=
$self
->
name
;
my
$search_broken
=
$self
->
search_known_broken
;
my
$search
;
TODO:
{
local
$TODO
=
$search_broken
if
$search_broken
;
$search
=
$self
->
_test_search_object_creation
();
}
my
(
$results
,
$sql
);
SKIP:
{
skip
"Can't run SQL without Search object"
,
2
if
!
$search
;
lives_ok
{
$sql
=
$search
->
getSQL
()
}
"$name: get SQL"
;
# This prevents warnings from DBD::mysql if we pass undef $sql,
# which happens if "new Bugzilla::Search" fails.
$sql
||=
''
;
$results
=
$self
->
_test_sql
(
$sql
);
}
$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
;
}
sub
_test_sql
{
my
(
$self
,
$sql
)
=
@_
;
my
$dbh
=
Bugzilla
->
dbh
;
my
$name
=
$self
->
name
;
my
$results
;
lives_ok
{
$results
=
$dbh
->
selectall_arrayref
(
$sql
)
}
"$name: Run SQL Query"
or
diag
(
$sql
);
return
$results
;
}
sub
_test_content
{
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
);
}
}
}
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
Dumper
(
$results
)
.
$self
->
debug_value
.
"\n\nSQL: $sql"
;
}
else
{
ok
(
!
$result_ids
{
$bug_id
},
"$name: does not contain bug $number ($bug_id)"
)
or
diag
Dumper
(
$results
)
.
$self
->
debug_value
.
"\n\nSQL: $sql"
;
}
}
}
1
;
\ No newline at end of file
xt/lib/Bugzilla/Test/Search/InjectionTest.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This module represents the SQL Injection tests that get run on a single
# operator/field combination for Bugzilla::Test::Search.
package
Bugzilla::Test::Search::
InjectionTest
;
use
base
qw(Bugzilla::Test::Search::FieldTest)
;
use
strict
;
use
warnings
;
use
Bugzilla::Test::Search::
Constants
;
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
}
||
[]
}
:
();
return
{}
if
grep
{
$_
eq
$self
->
field
}
@field_ok
;
my
$field_broken
=
INJECTION_BROKEN_FIELD
->
{
$self
->
field
};
# We don't want to auto-vivify $field_broken and thus make it true.
my
@operator_ok
=
$field_broken
?
@
{
$field_broken
->
{
operator_ok
}
||
[]
}
:
();
return
{}
if
grep
{
$_
eq
$self
->
operator
}
@operator_ok
;
return
$operator_broken
||
$field_broken
||
{};
}
sub
sql_error_ok
{
return
$_
[
0
]
->
_known_broken
->
{
sql_error
}
}
# Injection tests don't have to skip any fields.
sub
field_not_yet_implemented
{
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_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
;
\ No newline at end of file
xt/lib/Bugzilla/Test/Search/OperatorTest.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This module represents the tests that get run on a single operator
# from the TESTS constant in Bugzilla::Search::Test::Constants.
package
Bugzilla::Test::Search::
OperatorTest
;
use
strict
;
use
warnings
;
use
Bugzilla::Test::Search::
Constants
;
use
Bugzilla::Test::Search::
FieldTest
;
use
Bugzilla::Test::Search::
InjectionTest
;
use
Bugzilla::Test::Search::
OrTest
;
use
Bugzilla::Test::Search::
AndTest
;
###############
# Constructor #
###############
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
;
}
#############
# Accessors #
#############
# 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
}
}
}
# 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
();
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
();
}
}
}
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
();
}
1
;
\ No newline at end of file
xt/lib/Bugzilla/Test/Search/OrTest.pm
0 → 100644
View file @
87ea46f7
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# This test combines two field/operator combinations using OR in
# a single boolean chart.
package
Bugzilla::Test::Search::
OrTest
;
use
base
qw(Bugzilla::Test::Search::FieldTest)
;
use
Bugzilla::Test::Search::
Constants
;
use
Bugzilla::Test::Search::
FakeCGI
;
use
List::
MoreUtils
qw(any uniq)
;
use
constant
type
=>
'OR'
;
###############
# Constructor #
###############
sub
new
{
my
$class
=
shift
;
my
$self
=
{
field_tests
=>
[
@_
]
};
return
bless
$self
,
$class
;
}
#############
# Accessors #
#############
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
);
}
# 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
;
}
# Needed only for failure messages
sub
debug_value
{
my
(
$self
)
=
@_
;
my
@values
=
map
{
$_
->
field
.
' '
.
$_
->
debug_value
}
$self
->
field_tests
;
return
join
(
' '
.
$self
->
type
.
' '
,
@values
);
}
########################
# SKIP & TODO Messages #
########################
sub
_join_skip
{
OR_SKIP
}
sub
_join_broken_constant
{
OR_BROKEN
}
sub
field_not_yet_implemented
{
my
(
$self
)
=
@_
;
foreach
my
$test
(
$self
->
field_tests
)
{
if
(
grep
{
$_
eq
$test
->
field
}
$self
->
_join_skip
)
{
return
$test
->
field
.
" is not yet supported in OR tests"
;
}
}
return
$self
->
_join_messages
(
'field_not_yet_implemented'
);
}
sub
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'
);
}
sub
_join_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
)
=
@_
;
my
@results
;
foreach
my
$test
(
$self
->
field_tests
)
{
if
(
$test
->
bug_is_contained
(
$number
)
and
!
$test
->
contains_known_broken
(
$number
))
{
return
1
;
}
elsif
(
!
$test
->
bug_is_contained
(
$number
)
and
$test
->
contains_known_broken
(
$number
))
{
return
1
;
}
}
return
0
;
}
sub
contains_known_broken
{
my
(
$self
,
$number
)
=
@_
;
my
$join_broken
=
$self
->
_join_known_broken
;
if
(
my
$contains
=
$join_broken
->
{
contains
})
{
my
$contains_is_broken
=
grep
{
$_
==
$number
}
@$contains
;
if
(
$contains_is_broken
)
{
my
$name
=
$self
->
name
;
return
"$name contains $number is broken"
;
}
return
undef
;
}
return
$self
->
_join_contains_known_broken
(
$number
);
}
sub
_join_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
;
return
join
(
' AND '
,
@messages
);
}
return
undef
;
}
sub
_join_known_broken
{
my
(
$self
)
=
@_
;
my
$or_broken
=
$self
->
_join_broken_constant
;
foreach
my
$test
(
$self
->
field_tests
)
{
@or_broken_for
=
map
{
$_
->
join_broken
(
$or_broken
)
}
$self
->
field_tests
;
@or_broken_for
=
grep
{
defined
$_
}
@or_broken_for
;
last
if
!
@or_broken_for
;
$or_broken
=
$or_broken_for
[
0
];
}
return
$or_broken
;
}
##############################
# Bugzilla::Search arguments #
##############################
sub
search_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
=
new
Bugzilla::Test::Search::
FakeCGI
;
my
$chart
=
0
;
foreach
my
$item
(
@all_params
)
{
$params
->
param
(
"field0-0-$chart"
,
$item
->
param
(
'field0-0-0'
));
$params
->
param
(
"type0-0-$chart"
,
$item
->
param
(
'type0-0-0'
));
$params
->
param
(
"value0-0-$chart"
,
$item
->
param
(
'value0-0-0'
));
$chart
++
;
}
return
$params
;
}
1
;
\ No newline at end of file
xt/search.t
0 → 100644
View file @
87ea46f7
#!/usr/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Max Kanat-Alexander <mkanat@bugzilla.org>
# For a description of this test, see Bugzilla::Test::Search
# in xt/lib/.
use
strict
;
use
warnings
;
use
lib
qw(. xt/lib lib)
;
use
Bugzilla
;
use
Bugzilla::
Constants
;
use
Bugzilla::Test::
Search
;
use
Getopt::
Long
;
use
Pod::
Usage
;
use
Test::
More
;
my
%
switches
;
GetOptions
(
\%
switches
,
'operators=s'
,
'top-operators=s'
,
'long'
,
'add-custom-fields'
,
'help|h'
)
||
die
$@
;
pod2usage
(
verbose
=>
1
)
if
$switches
{
'help'
};
plan
skip_all
=>
"BZ_WRITE_TESTS environment variable not set"
if
!
$ENV
{
BZ_WRITE_TESTS
};
Bugzilla
->
usage_mode
(
USAGE_MODE_TEST
);
my
$test
=
new
Bugzilla::Test::
Search
(
\%
switches
);
plan
tests
=>
$test
->
num_tests
;
$test
->
run
();
__END__
=head1 NAME
search.t - Test L<Bugzilla::Search>
=head1 DESCRIPTION
This test tests L<Bugzilla::Search>.
Note that users may be prevented from writing new bugs, products, components,
etc. to your database while this test is running.
=head1 OPTIONS
=over
=item --long
Run AND and OR tests in addition to normal tests. Specifying
--long without also specifying L</--top-operators> is likely to
run your system out of memory.
=item --add-custom-fields
This adds every type of custom field to the database, so that they can
all be tested. Note that this B<CANNOT BE REVERSED>, so do not use this
switch on a production installation.
=item --operators=a,b,c
Limit the test to testing only the listed operators.
=item --top-operators=a,b,c
Limit the top-level tested operators to the following list. This
means that for normal tests, only the listed operators will be tested.
However, for OR and AND tests, all other operators will be tested
along with the operators you listed.
=item --help
Display this help.
=back
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment