hunt-searchengine-0.3.0.1: test/Hunt/QueryParserTests.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS -fno-warn-orphans #-}
{-# OPTIONS -fno-warn-missing-signatures #-}
{-# OPTIONS -fno-warn-missing-methods #-}
{-# OPTIONS -fno-warn-unused-matches #-}
{-# OPTIONS -fno-warn-type-defaults #-}
module Hunt.QueryParserTests
(queryParserTests)
where
import Control.Applicative
import Test.Framework hiding (Test)
import qualified Test.Framework as TF
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
import Test.HUnit
import Test.QuickCheck
import Control.Monad
import Data.Text (Text)
import qualified Data.Text as T
import Hunt.ClientInterface
import qualified Hunt.Query.Language.Parser as P
-- ----------------------------------------------------------------------------
-- query parser tests
--
queryParserTests :: [TF.Test]
queryParserTests = [ allProperties
, allUnitTests
]
allProperties = testGroup "Query Parser Properties"
[ testProperty "prop_ParseAnd" prop_ParseAnd
]
allUnitTests = testGroup "Query Parser Hunit tests" $ hUnitTestToTests $ TestList
[ TestLabel "And tests" andTests
, TestLabel "Or tests" orTests
, TestLabel "And Not tests" andNotTests
--, TestLabel "Not tests" notTests
, TestLabel "Specifier tests" specifierTests
, TestLabel "Case tests" caseTests
, TestLabel "Parenthese tests" parentheseTests
, TestLabel "Word tests" wordTests
, TestLabel "Phrase tests" phraseTests
, TestLabel "Fuzzy tests" fuzzyTests
, TestLabel "Range tests" rangeTests
, TestLabel "Boost tests" boostTests
]
---- ----------------------------------------------------------------------------
-- helper
--
a :: Query -> Query -> Query
a = qAnd
o :: Query -> Query -> Query
o = qOr
an :: Query -> Query -> Query
an = qAndNot
w :: Text -> Query
w = qWordNoCase
p :: Text -> Query
p = qPhraseNoCase
s :: [Text] -> Query -> Query
s = setContexts
cw :: Text -> Query
cw = qWord
cp :: Text -> Query
cp = qPhrase
fw :: Text -> Query
fw = setFuzzySearch . qWord
rg :: Text -> Text -> Query
rg = qRange
bst :: Weight -> Query -> Query
bst = setBoost
andTests :: Test
andTests = TestList
[ TestCase (assertEqual "Simple two term 'and' query"
(Right (a (w "abc") (w "def")))
(P.parseQuery "abc def"))
, TestCase (assertEqual "Concatenating 'and' terms"
(Right (a (w "abc") (a (w "def") (w "ghi"))))
(P.parseQuery "abc def ghi"))
, TestCase (assertEqual "Ignoring whitespace"
(Right (a (w "abc") (a (w "def") (a (w "ghi") (w "jkl")))))
(P.parseQuery " \rabc \r def \tghi \njkl \r\n "))
, TestCase (assertEqual "Priorities"
(Right (a (s ["wurst"] (w "abc")) (a (w "def") (a (w "ghi") (s ["wurst"] (w "jkl"))))))
(P.parseQuery "wurst:abc def ghi wurst:jkl"))
, TestCase (assertEqual "Confusing operator"
(Right (a (w "Apple") (a (w "Anna") (w "ANDroid"))))
(P.parseQuery "Apple Anna ANDroid"))
, TestCase (assertEqual "Explicit operator"
(Right (a (w "abc") (w "def")))
(P.parseQuery "abc AND def"))
]
orTests :: Test
orTests = TestList
[ TestCase (assertEqual "Simple two term 'or' query"
(Right (o (w "abc") (w "def")))
(P.parseQuery "abc OR def"))
, TestCase (assertEqual "Concatenating 'or' terms"
(Right (o (w "abc") (o (w "def") (w "ghi"))))
(P.parseQuery "abc OR def OR ghi"))
, TestCase (assertEqual "Ignoring whitespace"
(Right (o (w "abc") (o (w "def") (o (w "ghi") (w "jkl")))))
(P.parseQuery " \rabc \rOR def OR \tghi OR\njkl \r\n "))
, TestCase (assertEqual "Priorities"
(Right (o (s ["wurst"] (w "abc")) (o (w "def") (o (w "ghi") (s ["wurst"] (w "jkl"))))))
(P.parseQuery "wurst:abc OR def OR ghi OR wurst:jkl"))
, TestCase (assertEqual "Operator precedence"
(Right (o
(a
(s ["wurst"] (w "abc"))
(w "def")
)
(a
(w "ghi")
(s ["wurst"] (w "jkl"))
)
)
)
(P.parseQuery "wurst:abc def OR ghi wurst:jkl"))
, TestCase (assertEqual "Confusing operator"
(Right (a (w "Operation") (w "ORganism")))
(P.parseQuery "Operation ORganism"))
]
specifierTests :: Test
specifierTests = TestList
[ TestCase (assertEqual "Specifier with whitespace"
(Right (a (s ["wurst"] (w "abc")) (s ["batzen"] (w "def"))))
(P.parseQuery " wurst:\t abc \nbatzen : \r def "))
, TestCase (assertEqual "Specifier priority"
(Right (o (a (w "abc") (a (s ["wurst"] (w "def")) (s ["wurst"] (w "ghi")))) (s ["wurst"] (w "jkl"))))
(P.parseQuery "abc wurst: def wurst: ghi OR wurst: jkl"))
,TestCase (assertEqual "Specifier and brackets"
(Right (a (s ["wurst"] (a (w "abc") (a (w "def") (w "ghi")))) (s ["batzen"] (o (w "abc") (w "def")))))
(P.parseQuery "wurst: (abc def ghi) batzen: (abc OR def)"))
,TestCase (assertEqual "Specifier and brackets"
(Right (a (s ["wurst"] (a (w "abc") (a (w "def") (w "ghi")))) (s ["batzen"] (o (w "abc") (w "def")))))
(P.parseQuery "wurst: (abc def ghi) batzen: (abc OR def)"))
,TestCase (assertEqual "Specifier and space"
(Right (a (s ["wurst"] (a (w "abc") (a (w "def") (w "ghi")))) (s ["batzen"] (o (w "abc") (w "def")))))
(P.parseQuery "wurst \t: (abc def ghi) batzen \n : (abc OR def)"))
,TestCase (assertEqual "Specifier lists"
(Right (s ["wurst","batzen","schinken"] (a (w "abc") (a (w "def") (w "ghi")))))
(P.parseQuery "wurst,batzen,schinken: (abc def ghi)"))
,TestCase (assertEqual "Specifier lists with space"
(Right (s ["wurst","batzen","schinken"] (a (w "abc") (a (w "def") (w "ghi")))))
(P.parseQuery "wurst , \n batzen \t, schinken: (abc def ghi)"))
,TestCase (assertEqual "Specifier lists with phrase"
(Right (s ["wurst","batzen","schinken"] (p "this is A Test")))
(P.parseQuery "wurst , \n batzen \t, schinken: \"this is A Test\""))
]
andNotTests :: Test
andNotTests = TestList
[ TestCase (assertEqual "Simple two term 'and not' query"
(Right (an (w "abc") (w "def")))
(P.parseQuery "abc AND NOT def"))
, TestCase (assertEqual "Concatenating 'and' terms"
(Right (an (an (w "abc") (w "def")) (w "ghi")))
(P.parseQuery "abc AND NOT def AND NOT ghi"))
, TestCase (assertEqual "Ignoring whitespace"
(Right (an (an (an (w "abc") (w "def")) (w "ghi")) (w "jkl")))
(P.parseQuery " \rabc AND NOT\r def \tAND NOT ghi AND NOT \njkl \r\n "))
, TestCase (assertEqual "Priorities"
(Right (an (an (an (s ["wurst"] (w "abc")) (w "def")) (w "ghi")) (s ["wurst"] (w "jkl"))))
(P.parseQuery "wurst:abc AND NOT def AND NOT ghi AND NOT wurst:jkl"))
, TestCase (assertEqual "Confusing operator"
(Right (an (w "Apple") (a (w "Anna") (w "ANDNOTtingham"))))
(P.parseQuery "Apple AND NOT Anna ANDNOTtingham"))
]
{-
notTests :: Test
notTests = TestList
[ TestCase (assertEqual "Simple not query"
(Right (n (w "batzen")))
(P.parseQuery "NOT batzen"))
, TestCase (assertEqual "Operator precedence"
(Right (a (n (w "batzen")) (w "wurst")))
(P.parseQuery "NOT batzen wurst"))
, TestCase (assertEqual "Operator precedence with and"
(Right (a (w "test") (a (n (w "batzen")) (w "wurst"))))
(P.parseQuery "test NOT batzen wurst"))
, TestCase (assertEqual "Operator precedence with or"
(Right (o (w "test") (o (n (w "batzen")) (w "wurst"))))
(P.parseQuery "test OR NOT batzen OR wurst"))
, TestCase (assertEqual "Confusing operator"
(Right (a (w "Nail") (a (w "NOrthpole") (w "NOTtingham"))))
(P.parseQuery "Nail NOrthpole NOTtingham"))
]
-}
caseTests :: Test
caseTests = TestList
[ TestCase (assertEqual "Simple case-sensitive word"
(Right (cw "batzen"))
(P.parseQuery "!batzen"))
,TestCase (assertEqual "Simple case-sensitive phrase"
(Right (cp "this is a test"))
(P.parseQuery "!\"this is a test\""))
,TestCase (assertEqual "Case sensitive word with whitespace"
(Right (cw "test"))
(P.parseQuery " ! test"))
]
boostTests :: Test
boostTests = TestList
[ TestCase $ assertEqual "Boosting a word"
(Right (bst 9 $ w "word"))
( P.parseQuery "word^9")
, TestCase $ assertEqual "Boosting and more"
(Right (a (bst 2 $ w "foo") (w "bar")))
( P.parseQuery "foo^2 bar")
, TestCase $ assertEqual "Boosting a word with a proper float"
(Right (bst 9.5 $ w "word"))
( P.parseQuery "word^9.5")
, TestCase $ assertEqual "Boosting a phrase"
(Right (bst 9 $ p "word"))
( P.parseQuery "\"word\"^9")
, TestCase $ assertEqual "Boosting a binary query with parantheses"
(Right (bst 9 $ (o (a (w "w") (w "k")) (w "p"))))
( P.parseQuery "(w AND k OR p)^9")
, TestCase $ assertEqual "Boosting a context"
(Right (bst 9 $ s ["con"] (w "word")))
( P.parseQuery "(con:word)^9")
]
rangeTests :: Test
rangeTests = TestList
[ TestCase $ assertEqual "Simple Range Query without meta"
(Right (rg "30" "40"))
( P.parseQuery "[30 TO 40]")
, TestCase $ assertEqual "Range with context"
(Right (s ["con"] (rg "30" "40")))
( P.parseQuery "con:[30 TO 40]")
, TestCase $ assertEqual "Range with contexts"
(Right (s ["con1", "con2"] (rg "30" "40")))
( P.parseQuery "con1,con2:[30 TO 40]")
, TestCase $ assertEqual "complex query with ranges"
(Right (a (s ["con1"] (rg "30" "40")) (s ["con2"] (rg "59" "100"))))
( P.parseQuery "con1:[30 TO 40] AND con2:[59 TO 100]")
]
parentheseTests :: Test
parentheseTests = TestList
[ TestCase (assertEqual "Parentheses with effect"
(Right (a (w "abc") (o (w "def") (w "ghi"))))
(P.parseQuery "abc (def OR ghi)"))
, TestCase (assertEqual "Parentheses changing priority of OR"
(Right (a (o (w "abc") (w "def")) (w "ghi")))
(P.parseQuery "(abc OR def) ghi"))
, TestCase (assertEqual "Parentheses with whitespace and OR"
(Right (o (w "abc") (w "def")))
(P.parseQuery " ( abc OR def ) "))
, TestCase (assertEqual "Parentheses with whitespace and AND"
(Right (a (w "abc") (w "def")))
(P.parseQuery " ( abc def ) "))
]
fuzzyTests :: Test
fuzzyTests = TestList
[ TestCase (assertEqual "Simple fuzzy query"
(Right (fw "test"))
(P.parseQuery "~test"))
, TestCase (assertEqual "Fuzzy query with whitespace"
(Right (fw "test"))
(P.parseQuery " ~ test"))
]
wordTests :: Test
wordTests
= TestList
[ TestCase $
assertEqual "Quoted word query"
(Right (w "abc"))
(P.parseQuery "'abc'")
, TestCase $
assertEqual "Quoted word with whitespace query"
(Right (w "a b c"))
(P.parseQuery "'a b c'")
, TestCase $
assertEqual "Quoted word with single quotes query"
(Right (w "a'b"))
(P.parseQuery "'a\\'b'")
]
phraseTests :: Test
phraseTests = TestList
[ TestCase (assertEqual "Ignoring whitespace without case operator"
(Right (p "wurst schinken batzen"))
(P.parseQuery " \t \n \"wurst schinken batzen\" \t "))
, TestCase (assertEqual "Ignoring whitespace with case operator"
(Right (cp "wurst schinken batzen"))
(P.parseQuery " \t \n ! \"wurst schinken batzen\" \t "))
]
{--
instance Arbitrary Char where
arbitrary = oneof [choose ('\65', '\90') ,choose ('\97', '\122')]
shrink c = [ c' | c' <- ['a','b','c'], c' < c || not (isLower c) ]
--}
instance Arbitrary Text where
arbitrary = T.pack <$> arbitrary
shrink xs = T.pack <$> shrink (T.unpack xs)
instance Arbitrary Query where
arbitrary = sized query
query :: Int -> Gen Query
query num | num == 0 = liftM qWord word
| num < 0 = query (abs num)
| num > 0 = frequency [ (4, (setNoCaseSearch . qWord) <$> word)
, (1, qWord <$> word)
, (1, (setFuzzySearch . qWord) <$> word)
, (2, (setNoCaseSearch . qPhrase) <$> phrase)
, (1, qPhrase <$> phrase)
, (1, setContexts <$> specs <*> subQuery)
, (4, op <*> subQuery <*> subQuery)
]
query _ = error "Error in query generator!"
op = frequency [ (3, return qAnd)
, (1, return qOr)
, (1, return qAndNot)
]
subQuery = sized (\num -> query (num `div` 2))
specs = sequence [ word | i <- [1..2] ]
word :: Gen Text
word = fmap T.pack . listOf1 . elements $ concat [['0'..'9'], ['A'..'Z'], ['a'..'z']]
phrase = do
ws <- sequence [ word | i <- [1..3] ]
return (T.intercalate " " ws)
prop_ParseAnd q = (printQuery <$> (P.parseQuery $ T.unpack $ printQuery q)) == Right (printQuery q)