module Test.Ganeti.Query.Filter (testQuery_Filter) where
import Test.QuickCheck hiding (Result)
import Test.QuickCheck.Monadic
import qualified Data.Map as Map
import Data.List
import Text.JSON (showJSON)
import Test.Ganeti.TestHelper
import Test.Ganeti.TestCommon
import Test.Ganeti.Objects (genEmptyCluster)
import Ganeti.BasicTypes
import Ganeti.JSON
import Ganeti.Objects
import Ganeti.Query.Filter
import Ganeti.Query.Language
import Ganeti.Query.Query
import Ganeti.Utils (niceSort)
checkQueryResults :: ConfigData -> Query -> String
-> [[ResultEntry]] -> Property
checkQueryResults cfg qr descr expected = monadicIO $ do
result <- run (query cfg False qr) >>= resultProp
stop $ counterexample ("Inconsistent results in " ++ descr)
(qresData result ==? expected)
makeNodeQuery :: Filter FilterField -> Query
makeNodeQuery = Query (ItemTypeOpCode QRNode) ["name"]
expectBadQuery :: ConfigData -> Query -> String -> Property
expectBadQuery cfg qr descr = monadicIO $ do
result <- run (query cfg False qr)
case result of
Bad _ -> return ()
Ok a -> stop . failTest $ "Expected failure in " ++ descr ++
" but got " ++ show a
namesToResult :: [String] -> [[ResultEntry]]
namesToResult = map ((:[]) . ResultEntry RSNormal . Just . showJSON)
genClusterNames :: Int -> Int -> Gen (ConfigData, [String])
genClusterNames min_nodes max_nodes = do
numnodes <- choose (min_nodes, max_nodes)
cfg <- genEmptyCluster numnodes
return (cfg, niceSort . Map.keys . fromContainer $ configNodes cfg)
prop_node_single_filter :: Property
prop_node_single_filter =
forAll (genClusterNames 1 maxNodes) $ \(cfg, allnodes) ->
forAll (elements allnodes) $ \nname ->
let fvalue = QuotedString nname
buildflt n = n "name" fvalue
expsingle = namesToResult [nname]
othernodes = nname `delete` allnodes
expnot = namesToResult othernodes
test_query = checkQueryResults cfg . makeNodeQuery
in conjoin
[ test_query (buildflt EQFilter) "single-name 'EQ' filter" expsingle
, test_query (NotFilter (buildflt EQFilter))
"single-name 'NOT EQ' filter" expnot
, test_query (AndFilter [buildflt LTFilter, buildflt GTFilter])
"single-name 'AND [LT,GT]' filter" []
, test_query (AndFilter [buildflt LEFilter, buildflt GEFilter])
"single-name 'And [LE,GE]' filter" expsingle
]
prop_node_many_filter :: Property
prop_node_many_filter =
forAll (genClusterNames 2 maxNodes) $ \(cfg, nnames) ->
let eqfilter = map (EQFilter "name" . QuotedString) nnames
alln = namesToResult nnames
test_query = checkQueryResults cfg . makeNodeQuery
num_zero = NumericValue 0
in conjoin
[ test_query (OrFilter eqfilter) "all nodes 'Or' name filter" alln
, test_query (AndFilter eqfilter) "all nodes 'And' name filter" []
, test_query (EQFilter "pinst_cnt" num_zero) "pinst_cnt 'Eq' 0" alln
, test_query (GTFilter "sinst_cnt" num_zero) "sinst_cnt 'GT' 0" []
]
prop_node_name_ordering :: Property
prop_node_name_ordering =
forAll (genClusterNames 2 6) $ \(cfg, nnames) ->
forAll (elements (subsequences nnames)) $ \sorted_nodes ->
forAll (elements (permutations sorted_nodes)) $ \chosen_nodes ->
let orfilter = OrFilter $ map (EQFilter "name" . QuotedString) chosen_nodes
alln = namesToResult chosen_nodes
all_sorted = namesToResult $ niceSort chosen_nodes
test_query = checkQueryResults cfg . makeNodeQuery
in conjoin
[ test_query orfilter "simple filter/requested" alln
, test_query (AndFilter [orfilter]) "complex filter/sorted" all_sorted
]
prop_node_regex_filter :: Property
prop_node_regex_filter =
forAll (genClusterNames 0 maxNodes) $ \(cfg, nnames) ->
case mkRegex ".*"::Result FilterRegex of
Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
Ok rx ->
checkQueryResults cfg (makeNodeQuery (RegexpFilter "name" rx))
"rows for all nodes regexp filter" $ namesToResult nnames
prop_node_bad_filter :: String -> Int -> Property
prop_node_bad_filter rndname rndint =
forAll (genClusterNames 1 maxNodes) $ \(cfg, _) ->
let test_query = expectBadQuery cfg . makeNodeQuery
string_value = QuotedString rndname
numeric_value = NumericValue $ fromIntegral rndint
in case mkRegex ".*"::Result FilterRegex of
Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
Ok rx ->
conjoin
[ test_query (RegexpFilter "offline" rx)
"regex filter against boolean field"
, test_query (EQFilter "name" numeric_value)
"numeric value eq against string field"
, test_query (TrueFilter "name")
"true filter against string field"
, test_query (EQFilter "offline" string_value)
"quoted string eq against boolean field"
, test_query (ContainsFilter "name" string_value)
"quoted string in non-list field"
, test_query (ContainsFilter "name" numeric_value)
"numeric value in non-list field"
]
prop_makeSimpleFilter :: Property
prop_makeSimpleFilter =
forAll (resize 10 $ listOf1 genName) $ \names ->
forAll (resize 10 $ listOf1 arbitrary) $ \ids ->
forAll genName $ \namefield ->
conjoin [ counterexample "test expected names" $
makeSimpleFilter namefield (map Left names) ==?
OrFilter (map (EQFilter namefield . QuotedString) names)
, counterexample "test expected IDs" $
makeSimpleFilter namefield (map Right ids) ==?
OrFilter (map (EQFilter namefield . NumericValue) ids)
, counterexample "test empty names" $
makeSimpleFilter namefield [] ==? EmptyFilter
]
testSuite "Query/Filter"
[ 'prop_node_single_filter
, 'prop_node_many_filter
, 'prop_node_name_ordering
, 'prop_node_regex_filter
, 'prop_node_bad_filter
, 'prop_makeSimpleFilter
]