From 15f4c8ca1fa047af3b571303603dfab315624996 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Wed, 27 May 2009 22:22:38 +0100
Subject: [PATCH] Add test infrastructure and initial tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch adds a QuickCheck-based test infrastructure and initial tests
based on it. The PeerMap module has a 100% coverage ☺

Side-note: one has to read the source of QuickCheck to see how to use it
(especially the Batch submodule), the docs are not enough…
---
 .gitignore            |   6 ++-
 Ganeti/HTools/Node.hs |   2 +-
 Ganeti/HTools/QC.hs   | 101 ++++++++++++++++++++++++++++++++++++++++++
 Makefile              |  22 +++++++--
 test.hs               |  27 +++++++++++
 5 files changed, 153 insertions(+), 5 deletions(-)
 create mode 100644 Ganeti/HTools/QC.hs
 create mode 100644 test.hs

diff --git a/.gitignore b/.gitignore
index 9f2bc4e13..65824c5ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
-apidoc
+/apidoc/
+/.hpc/
+/coverage/
 
 *.o
 *.patch
@@ -15,8 +17,10 @@ hn1
 hbal
 hscan
 hail
+test
 *.prof*
 *.stat
+*.tix
 
 version
 Version.hs
diff --git a/Ganeti/HTools/Node.hs b/Ganeti/HTools/Node.hs
index 68f8300bb..05930cdf3 100644
--- a/Ganeti/HTools/Node.hs
+++ b/Ganeti/HTools/Node.hs
@@ -5,7 +5,7 @@
 -}
 
 module Ganeti.HTools.Node
-    ( Node(failN1, name, idx, t_mem, n_mem, f_mem, t_dsk, f_dsk,
+    ( Node(failN1, name, idx, t_mem, n_mem, f_mem, r_mem, t_dsk, f_dsk,
            p_mem, p_dsk, p_rem,
            plist, slist, offline)
     , List
diff --git a/Ganeti/HTools/QC.hs b/Ganeti/HTools/QC.hs
new file mode 100644
index 000000000..c861358f5
--- /dev/null
+++ b/Ganeti/HTools/QC.hs
@@ -0,0 +1,101 @@
+module Ganeti.HTools.QC
+    where
+
+import Test.QuickCheck
+import Data.Maybe
+import qualified Ganeti.HTools.CLI as CLI
+import qualified Ganeti.HTools.Cluster as Cluster
+import qualified Ganeti.HTools.Container as Container
+import qualified Ganeti.HTools.IAlloc as IAlloc
+import qualified Ganeti.HTools.Instance as Instance
+import qualified Ganeti.HTools.Loader as Loader
+import qualified Ganeti.HTools.Node as Node
+import qualified Ganeti.HTools.PeerMap as PeerMap
+import qualified Ganeti.HTools.Rapi as Rapi
+import qualified Ganeti.HTools.Text as Text
+import qualified Ganeti.HTools.Types as Types
+import qualified Ganeti.HTools.Utils as Utils
+
+-- copied from the introduction to quickcheck
+instance Arbitrary Char where
+    arbitrary     = choose ('\32', '\128')
+
+-- let's generate a random instance
+instance Arbitrary Instance.Instance where
+    arbitrary = do
+      name <- arbitrary
+      mem <- choose(0, 100)
+      dsk <- choose(0, 100)
+      run_st <- arbitrary
+      pn <- arbitrary
+      sn <- arbitrary
+      return $ Instance.create name mem dsk run_st pn sn
+
+-- and a random node
+instance Arbitrary Node.Node where
+    arbitrary = do
+      name <- arbitrary
+      mem_t <- arbitrary
+      mem_f <- choose (0, mem_t)
+      mem_n <- choose (0, mem_t - mem_f)
+      dsk_t <- arbitrary
+      dsk_f <- choose (0, dsk_t)
+      offl <- arbitrary
+      npeers <- choose (0, 100)
+      let n = Node.create name (fromIntegral mem_t) mem_n mem_f
+              (fromIntegral dsk_t) dsk_f offl
+          n' = Node.buildPeers n Container.empty npeers
+      return n'
+
+-- | Make sure add is idempotent
+prop_PeerMap_addIdempotent pmap key elem =
+    fn puniq == fn (fn puniq)
+    where fn = PeerMap.add key elem
+          puniq = PeerMap.accumArray const pmap
+          _types = (pmap::PeerMap.PeerMap,
+                    key::PeerMap.Key, elem::PeerMap.Elem)
+
+-- | Make sure remove is idempotent
+prop_PeerMap_removeIdempotent pmap key =
+    fn puniq == fn (fn puniq)
+    where fn = PeerMap.remove key
+          puniq = PeerMap.accumArray const pmap
+          _types = (pmap::PeerMap.PeerMap,
+                    key::PeerMap.Key)
+
+-- | Make sure a missing item returns 0
+prop_PeerMap_findMissing pmap key =
+    PeerMap.find key (PeerMap.remove key puniq) == 0
+    where fn = PeerMap.remove key
+          puniq = PeerMap.accumArray const pmap
+          _types = (pmap::PeerMap.PeerMap,
+                    key::PeerMap.Key)
+
+-- | Make sure an added item is found
+prop_PeerMap_addFind pmap key elem =
+    PeerMap.find key (PeerMap.add key elem puniq) == elem
+    where puniq = PeerMap.accumArray const pmap
+          _types = (pmap::PeerMap.PeerMap,
+                    key::PeerMap.Key, elem::PeerMap.Elem)
+
+-- | Manual check that maxElem returns the maximum indeed, or 0 for null
+prop_PeerMap_maxElem pmap =
+    PeerMap.maxElem puniq == if null puniq then 0
+                             else (maximum . snd . unzip) puniq
+    where
+          puniq = PeerMap.accumArray const pmap
+          _types = pmap::PeerMap.PeerMap
+
+prop_Node_addPri node inst = (Instance.mem inst >= Node.f_mem node ||
+                              Instance.dsk inst >= Node.f_dsk node) &&
+                             (not $ Node.failN1 node)
+                             ==>
+                             isNothing(Node.addPri node inst)
+    where _types = (node::Node.Node, inst::Instance.Instance)
+
+prop_Node_addSec node inst pdx =
+    (Instance.mem inst >= (Node.f_mem node - Node.r_mem node) ||
+     Instance.dsk inst >= Node.f_dsk node) &&
+    (not $ Node.failN1 node)
+    ==> isNothing(Node.addSec node inst pdx)
+        where _types = (node::Node.Node, inst::Instance.Instance, pdx::Int)
diff --git a/Makefile b/Makefile
index b349a8ca1..be3c899c7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,20 @@
-HPROGS = hbal hn1 hscan hail
+HPROGS = hbal hn1 hscan hail test
 HSRCS := $(wildcard Ganeti/HTools/*.hs)
 HDDIR = apidoc
 
 DOCS = README.html NEWS.html
 
+HFLAGS = -O2 -W -fwarn-monomorphism-restriction -fwarn-tabs
+HEXTRA =
+
+HPCEXCL = --exclude Main --exclude Ganeti.HTools.QC
+
 # Haskell rules
 
 all: $(HPROGS)
 
 $(HPROGS): %: %.hs Ganeti/HTools/Version.hs $(HSRCS) Makefile
-	ghc --make -O2 -W $@
+	ghc --make $(HFLAGS) $(HEXTRA) $@
 
 $(DOCS) : %.html : %
 	rst2html $< $@
@@ -53,4 +58,15 @@ dist: Ganeti/HTools/Version.hs version doc
 	gzip -v9 $$ANAME ; \
 	tar tzvf $$ANAME.gz
 
-.PHONY : all doc maintainer-clean clean dist
+check:
+	rm -f *.tix *.mix test
+	$(MAKE) HEXTRA=-fhpc test
+	./test
+ifeq ($(T),markup)
+	mkdir -p coverage
+	hpc markup --destdir=coverage test $(HPCEXCL)
+else
+	hpc report test $(HPCEXCL)
+endif
+
+.PHONY : all doc maintainer-clean clean dist check
diff --git a/test.hs b/test.hs
new file mode 100644
index 000000000..fbe765af6
--- /dev/null
+++ b/test.hs
@@ -0,0 +1,27 @@
+{-| Unittest runner for htools
+
+-}
+
+module Main(main) where
+
+import Test.QuickCheck.Batch
+import Ganeti.HTools.QC
+
+options = TestOptions
+      { no_of_tests         = 500
+      , length_of_tests     = 5
+      , debug_tests         = False }
+
+main = do
+  runTests "PeerMap" options
+       [ run prop_PeerMap_addIdempotent
+       , run prop_PeerMap_removeIdempotent
+       , run prop_PeerMap_maxElem
+       , run prop_PeerMap_addFind
+       , run prop_PeerMap_findMissing
+       ]
+
+  runTests "Node" options
+       [ run prop_Node_addPri
+       , run prop_Node_addSec
+       ]
-- 
GitLab