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