Move JSON.hs and Compat.hs out from under HTools/
[ganeti-local] / htest / Test / Ganeti / TestCommon.hs
1 {-| Unittest helpers for ganeti-htools.
2
3 -}
4
5 {-
6
7 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 02110-1301, USA.
23
24 -}
25
26 module Test.Ganeti.TestCommon where
27
28 import Control.Applicative
29 import Control.Exception (catchJust)
30 import Control.Monad
31 import Data.List
32 import qualified Test.HUnit as HUnit
33 import Test.QuickCheck
34 import System.Environment (getEnv)
35 import System.Exit (ExitCode(..))
36 import System.IO.Error (isDoesNotExistError)
37 import System.Process (readProcessWithExitCode)
38
39 -- * Constants
40
41 -- | Maximum memory (1TiB, somewhat random value).
42 maxMem :: Int
43 maxMem = 1024 * 1024
44
45 -- | Maximum disk (8TiB, somewhat random value).
46 maxDsk :: Int
47 maxDsk = 1024 * 1024 * 8
48
49 -- | Max CPUs (1024, somewhat random value).
50 maxCpu :: Int
51 maxCpu = 1024
52
53 -- | Max vcpu ratio (random value).
54 maxVcpuRatio :: Double
55 maxVcpuRatio = 1024.0
56
57 -- | Max spindle ratio (random value).
58 maxSpindleRatio :: Double
59 maxSpindleRatio = 1024.0
60
61 -- | Max nodes, used just to limit arbitrary instances for smaller
62 -- opcode definitions (e.g. list of nodes in OpTestDelay).
63 maxNodes :: Int
64 maxNodes = 32
65
66 -- | Max opcodes or jobs in a submit job and submit many jobs.
67 maxOpCodes :: Int
68 maxOpCodes = 16
69
70 -- * Helper functions
71
72 -- | Checks for equality with proper annotation.
73 (==?) :: (Show a, Eq a) => a -> a -> Property
74 (==?) x y = printTestCase
75             ("Expected equality, but '" ++
76              show x ++ "' /= '" ++ show y ++ "'") (x == y)
77 infix 3 ==?
78
79 -- | Show a message and fail the test.
80 failTest :: String -> Property
81 failTest msg = printTestCase msg False
82
83 -- | Return the python binary to use. If the PYTHON environment
84 -- variable is defined, use its value, otherwise use just \"python\".
85 pythonCmd :: IO String
86 pythonCmd = catchJust (guard . isDoesNotExistError)
87             (getEnv "PYTHON") (const (return "python"))
88
89 -- | Run Python with an expression, returning the exit code, standard
90 -- output and error.
91 runPython :: String -> String -> IO (ExitCode, String, String)
92 runPython expr stdin = do
93   py_binary <- pythonCmd
94   readProcessWithExitCode py_binary ["-c", expr] stdin
95
96 -- | Check python exit code, and fail via HUnit assertions if
97 -- non-zero. Otherwise, return the standard output.
98 checkPythonResult :: (ExitCode, String, String) -> IO String
99 checkPythonResult (py_code, py_stdout, py_stderr) = do
100   HUnit.assertEqual ("python exited with error: " ++ py_stderr)
101        ExitSuccess py_code
102   return py_stdout
103
104 -- * Arbitrary instances
105
106 -- | Defines a DNS name.
107 newtype DNSChar = DNSChar { dnsGetChar::Char }
108
109 instance Arbitrary DNSChar where
110   arbitrary = do
111     x <- elements (['a'..'z'] ++ ['0'..'9'] ++ "_-")
112     return (DNSChar x)
113
114 instance Show DNSChar where
115   show = show . dnsGetChar
116
117 -- | Generates a single name component.
118 getName :: Gen String
119 getName = do
120   n <- choose (1, 64)
121   dn <- vector n
122   return (map dnsGetChar dn)
123
124 -- | Generates an entire FQDN.
125 getFQDN :: Gen String
126 getFQDN = do
127   ncomps <- choose (1, 4)
128   names <- vectorOf ncomps getName
129   return $ intercalate "." names
130
131 -- | Combinator that generates a 'Maybe' using a sub-combinator.
132 getMaybe :: Gen a -> Gen (Maybe a)
133 getMaybe subgen = do
134   bool <- arbitrary
135   if bool
136     then Just <$> subgen
137     else return Nothing
138
139 -- | Defines a tag type.
140 newtype TagChar = TagChar { tagGetChar :: Char }
141
142 -- | All valid tag chars. This doesn't need to match _exactly_
143 -- Ganeti's own tag regex, just enough for it to be close.
144 tagChar :: [Char]
145 tagChar = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ".+*/:@-"
146
147 instance Arbitrary TagChar where
148   arbitrary = do
149     c <- elements tagChar
150     return (TagChar c)
151
152 -- | Generates a tag
153 genTag :: Gen [TagChar]
154 genTag = do
155   -- the correct value would be C.maxTagLen, but that's way too
156   -- verbose in unittests, and at the moment I don't see any possible
157   -- bugs with longer tags and the way we use tags in htools
158   n <- choose (1, 10)
159   vector n
160
161 -- | Generates a list of tags (correctly upper bounded).
162 genTags :: Gen [String]
163 genTags = do
164   -- the correct value would be C.maxTagsPerObj, but per the comment
165   -- in genTag, we don't use tags enough in htools to warrant testing
166   -- such big values
167   n <- choose (0, 10::Int)
168   tags <- mapM (const genTag) [1..n]
169   return $ map (map tagGetChar) tags
170
171 -- | Generates a fields list. This uses the same character set as a
172 -- DNS name (just for simplicity).
173 getFields :: Gen [String]
174 getFields = do
175   n <- choose (1, 32)
176   vectorOf n getName
177
178 -- | Generates a list of a given size with non-duplicate elements.
179 genUniquesList :: (Eq a, Arbitrary a) => Int -> Gen [a]
180 genUniquesList cnt =
181   foldM (\lst _ -> do
182            newelem <- arbitrary `suchThat` (`notElem` lst)
183            return (newelem:lst)) [] [1..cnt]
184
185 newtype SmallRatio = SmallRatio Double deriving Show
186 instance Arbitrary SmallRatio where
187   arbitrary = do
188     v <- choose (0, 1)
189     return $ SmallRatio v