1 {-# LANGUAGE TemplateHaskell #-}
2 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 {-| Unittests for ganeti-htools.
10 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful, but
18 WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29 module Test.Ganeti.Query.Filter (testQuery_Filter) where
31 import Test.QuickCheck hiding (Result)
32 import Test.QuickCheck.Monadic
34 import qualified Data.Map as Map
36 import Text.JSON (showJSON)
38 import Test.Ganeti.TestHelper
39 import Test.Ganeti.TestCommon
40 import Test.Ganeti.Objects (genEmptyCluster)
42 import Ganeti.BasicTypes
45 import Ganeti.Query.Filter
46 import Ganeti.Query.Language
47 import Ganeti.Query.Query
48 import Ganeti.Utils (niceSort)
52 -- | Run a query and check that we got a specific response.
53 checkQueryResults :: ConfigData -> Query -> String
54 -> [[ResultEntry]] -> Property
55 checkQueryResults cfg qr descr expected = monadicIO $ do
56 result <- run (query cfg False qr) >>= resultProp
57 stop $ printTestCase ("Inconsistent results in " ++ descr)
58 (qresData result ==? expected)
60 -- | Makes a node name query, given a filter.
61 makeNodeQuery :: Filter FilterField -> Query
62 makeNodeQuery = Query (ItemTypeOpCode QRNode) ["name"]
64 -- | Checks if a given operation failed.
65 expectBadQuery :: ConfigData -> Query -> String -> Property
66 expectBadQuery cfg qr descr = monadicIO $ do
67 result <- run (query cfg False qr)
70 Ok a -> stop . failTest $ "Expected failure in " ++ descr ++
73 -- | A helper to construct a list of results from an expected names list.
74 namesToResult :: [String] -> [[ResultEntry]]
75 namesToResult = map ((:[]) . ResultEntry RSNormal . Just . showJSON)
77 -- | Generates a cluster and returns its node names too.
78 genClusterNames :: Int -> Int -> Gen (ConfigData, [String])
79 genClusterNames min_nodes max_nodes = do
80 numnodes <- choose (min_nodes, max_nodes)
81 cfg <- genEmptyCluster numnodes
82 return (cfg, niceSort . Map.keys . fromContainer $ configNodes cfg)
86 -- | Tests single node filtering: eq should return it, and (lt and gt)
88 prop_node_single_filter :: Property
89 prop_node_single_filter =
90 forAll (genClusterNames 1 maxNodes) $ \(cfg, allnodes) ->
91 forAll (elements allnodes) $ \nname ->
92 let fvalue = QuotedString nname
93 buildflt n = n "name" fvalue
94 expsingle = namesToResult [nname]
95 othernodes = nname `delete` allnodes
96 expnot = namesToResult othernodes
97 test_query = checkQueryResults cfg . makeNodeQuery
99 [ test_query (buildflt EQFilter) "single-name 'EQ' filter" expsingle
100 , test_query (NotFilter (buildflt EQFilter))
101 "single-name 'NOT EQ' filter" expnot
102 , test_query (AndFilter [buildflt LTFilter, buildflt GTFilter])
103 "single-name 'AND [LT,GT]' filter" []
104 , test_query (AndFilter [buildflt LEFilter, buildflt GEFilter])
105 "single-name 'And [LE,GE]' filter" expsingle
108 -- | Tests node filtering based on name equality: many 'OrFilter'
109 -- should return all results combined, many 'AndFilter' together
110 -- should return nothing. Note that we need at least 2 nodes so that
111 -- the 'AndFilter' case breaks.
112 prop_node_many_filter :: Property
113 prop_node_many_filter =
114 forAll (genClusterNames 2 maxNodes) $ \(cfg, nnames) ->
115 let eqfilter = map (EQFilter "name" . QuotedString) nnames
116 alln = namesToResult nnames
117 test_query = checkQueryResults cfg . makeNodeQuery
118 num_zero = NumericValue 0
120 [ test_query (OrFilter eqfilter) "all nodes 'Or' name filter" alln
121 , test_query (AndFilter eqfilter) "all nodes 'And' name filter" []
122 -- this next test works only because genEmptyCluster generates a
123 -- cluster with no instances
124 , test_query (EQFilter "pinst_cnt" num_zero) "pinst_cnt 'Eq' 0" alln
125 , test_query (GTFilter "sinst_cnt" num_zero) "sinst_cnt 'GT' 0" []
128 -- | Tests name ordering consistency: requesting a 'simple filter'
129 -- results in identical name ordering as the wanted names, requesting
130 -- a more complex filter results in a niceSort-ed order.
131 prop_node_name_ordering :: Property
132 prop_node_name_ordering =
133 forAll (genClusterNames 2 6) $ \(cfg, nnames) ->
134 forAll (elements (subsequences nnames)) $ \sorted_nodes ->
135 forAll (elements (permutations sorted_nodes)) $ \chosen_nodes ->
136 let orfilter = OrFilter $ map (EQFilter "name" . QuotedString) chosen_nodes
137 alln = namesToResult chosen_nodes
138 all_sorted = namesToResult $ niceSort chosen_nodes
139 test_query = checkQueryResults cfg . makeNodeQuery
141 [ test_query orfilter "simple filter/requested" alln
142 , test_query (AndFilter [orfilter]) "complex filter/sorted" all_sorted
145 -- | Tests node regex filtering. This is a very basic test :(
146 prop_node_regex_filter :: Property
147 prop_node_regex_filter =
148 forAll (genClusterNames 0 maxNodes) $ \(cfg, nnames) ->
149 case mkRegex ".*"::Result FilterRegex of
150 Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
152 checkQueryResults cfg (makeNodeQuery (RegexpFilter "name" rx))
153 "rows for all nodes regexp filter" $ namesToResult nnames
155 -- | Tests node regex filtering. This is a very basic test :(
156 prop_node_bad_filter :: String -> Int -> Property
157 prop_node_bad_filter rndname rndint =
158 forAll (genClusterNames 1 maxNodes) $ \(cfg, _) ->
159 let test_query = expectBadQuery cfg . makeNodeQuery
160 string_value = QuotedString rndname
161 numeric_value = NumericValue $ fromIntegral rndint
162 in case mkRegex ".*"::Result FilterRegex of
163 Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
166 [ test_query (RegexpFilter "offline" rx)
167 "regex filter against boolean field"
168 , test_query (EQFilter "name" numeric_value)
169 "numeric value eq against string field"
170 , test_query (TrueFilter "name")
171 "true filter against string field"
172 , test_query (EQFilter "offline" string_value)
173 "quoted string eq against boolean field"
174 , test_query (ContainsFilter "name" string_value)
175 "quoted string in non-list field"
176 , test_query (ContainsFilter "name" numeric_value)
177 "numeric value in non-list field"
180 -- | Tests make simple filter.
181 prop_makeSimpleFilter :: Property
182 prop_makeSimpleFilter =
183 forAll (resize 10 $ listOf1 genName) $ \names ->
184 forAll (resize 10 $ listOf1 arbitrary) $ \ids ->
185 forAll genName $ \namefield ->
186 conjoin [ printTestCase "test expected names" $
187 makeSimpleFilter namefield (map Left names) ==?
188 OrFilter (map (EQFilter namefield . QuotedString) names)
189 , printTestCase "test expected IDs" $
190 makeSimpleFilter namefield (map Right ids) ==?
191 OrFilter (map (EQFilter namefield . NumericValue) ids)
192 , printTestCase "test empty names" $
193 makeSimpleFilter namefield [] ==? EmptyFilter
196 testSuite "Query/Filter"
197 [ 'prop_node_single_filter
198 , 'prop_node_many_filter
199 , 'prop_node_name_ordering
200 , 'prop_node_regex_filter
201 , 'prop_node_bad_filter
202 , 'prop_makeSimpleFilter