From 2d52359bb42f0aab3035837b70f1dd3b864ad27d Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Fri, 5 Oct 2012 01:43:24 +0100
Subject: [PATCH] Cleanup/expand the filter/query tests

This patch cleans up duplicate code in Test.Ganeti.Query.Filter and
then adds a test for names consistency with Python's code behaviour
(stable ordering for simple filters and otherwise niceSort'ed
ordering).

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>
---
 htest/Test/Ganeti/Query/Filter.hs | 70 +++++++++++++++++++------------
 1 file changed, 44 insertions(+), 26 deletions(-)

diff --git a/htest/Test/Ganeti/Query/Filter.hs b/htest/Test/Ganeti/Query/Filter.hs
index 8f7044b59..28947415d 100644
--- a/htest/Test/Ganeti/Query/Filter.hs
+++ b/htest/Test/Ganeti/Query/Filter.hs
@@ -70,21 +70,30 @@ expectBadQuery cfg qr descr = monadicIO $ do
     Ok a  -> stop . failTest $ "Expected failure in " ++ descr ++
                                " but got " ++ show a
 
+-- | A helper to construct a list of results from an expected names list.
+namesToResult :: [String] -> [[ResultEntry]]
+namesToResult = map ((:[]) . ResultEntry RSNormal . Just . showJSON)
+
+-- | Generates a cluster and returns its node names too.
+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)
+
 -- * Test cases
 
 -- | Tests single node filtering: eq should return it, and (lt and gt)
 -- should fail.
 prop_node_single_filter :: Property
 prop_node_single_filter =
-  forAll (choose (1, maxNodes)) $ \numnodes ->
-  forAll (genEmptyCluster numnodes) $ \cfg ->
-  let allnodes = niceSort . Map.keys . fromContainer $ configNodes cfg in
+  forAll (genClusterNames 1 maxNodes) $ \(cfg, allnodes) ->
   forAll (elements allnodes) $ \nname ->
   let fvalue = QuotedString nname
       buildflt n = n "name" fvalue
-      expsingle = [[ResultEntry RSNormal (Just (showJSON nname))]]
+      expsingle = namesToResult [nname]
       othernodes = nname `delete` allnodes
-      expnot = map ((:[]) . ResultEntry RSNormal . Just . showJSON) othernodes
+      expnot = namesToResult othernodes
       test_query = checkQueryResults cfg . makeNodeQuery
   in conjoin
        [ test_query (buildflt EQFilter) "single-name 'EQ' filter" expsingle
@@ -102,11 +111,9 @@ prop_node_single_filter =
 -- the 'AndFilter' case breaks.
 prop_node_many_filter :: Property
 prop_node_many_filter =
-  forAll (choose (2, maxNodes)) $ \numnodes ->
-  forAll (genEmptyCluster numnodes) $ \cfg ->
-  let nnames = niceSort . Map.keys . fromContainer $ configNodes cfg
-      eqfilter = map (EQFilter "name" . QuotedString) nnames
-      alln = map ((:[]) . ResultEntry RSNormal . Just . showJSON) nnames
+  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
@@ -118,31 +125,41 @@ prop_node_many_filter =
      , test_query (GTFilter "sinst_cnt" num_zero) "sinst_cnt 'GT' 0" []
      ]
 
+-- | Tests name ordering consistency: requesting a 'simple filter'
+-- results in identical name ordering as the wanted names, requesting
+-- a more complex filter results in a niceSort-ed order.
+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
+     ]
+
 -- | Tests node regex filtering. This is a very basic test :(
 prop_node_regex_filter :: Property
 prop_node_regex_filter =
-  forAll (choose (0, maxNodes)) $ \numnodes ->
-  forAll (genEmptyCluster numnodes) $ \cfg ->
-  let nnames = niceSort . Map.keys . fromContainer $ configNodes cfg
-      expected = map ((:[]) . ResultEntry RSNormal . Just . showJSON) nnames
-      regex = mkRegex ".*"::Result FilterRegex
-  in case regex of
-       Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
-       Ok rx ->
-         checkQueryResults cfg (makeNodeQuery (RegexpFilter "name" rx))
-           "rows for all nodes regexp filter"
-           expected
+  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
 
 -- | Tests node regex filtering. This is a very basic test :(
 prop_node_bad_filter :: String -> Int -> Property
 prop_node_bad_filter rndname rndint =
-  forAll (choose (1, maxNodes)) $ \numnodes ->
-  forAll (genEmptyCluster numnodes) $ \cfg ->
-  let regex = mkRegex ".*"::Result FilterRegex
-      test_query = expectBadQuery cfg . makeNodeQuery
+  forAll (genClusterNames 1 maxNodes) $ \(cfg, _) ->
+  let test_query = expectBadQuery cfg . makeNodeQuery
       string_value = QuotedString rndname
       numeric_value = NumericValue $ fromIntegral rndint
-  in case regex of
+  in case mkRegex ".*"::Result FilterRegex of
        Bad msg -> failTest $ "Can't build regex?! Error: " ++ msg
        Ok rx ->
          conjoin
@@ -175,6 +192,7 @@ prop_makeSimpleFilter =
 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
-- 
GitLab