1 {-# LANGUAGE TemplateHaskell #-}
2 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 {-| Unittests for ganeti-htools.
10 Copyright (C) 2009, 2010, 2011, 2012, 2013 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.OpCodes
34 import Test.HUnit as HUnit
35 import Test.QuickCheck as QuickCheck
37 import Control.Applicative
41 import qualified Data.Map as Map
42 import qualified Text.JSON as J
43 import Text.Printf (printf)
45 import Test.Ganeti.TestHelper
46 import Test.Ganeti.TestCommon
47 import Test.Ganeti.Types ()
48 import Test.Ganeti.Query.Language
50 import Ganeti.BasicTypes
51 import qualified Ganeti.Constants as C
52 import qualified Ganeti.OpCodes as OpCodes
54 import Ganeti.OpParams
57 {-# ANN module "HLint: ignore Use camelCase" #-}
59 -- * Arbitrary instances
61 instance Arbitrary OpCodes.TagObject where
62 arbitrary = oneof [ OpCodes.TagInstance <$> genFQDN
63 , OpCodes.TagNode <$> genFQDN
64 , OpCodes.TagGroup <$> genFQDN
65 , pure OpCodes.TagCluster
68 $(genArbitrary ''OpCodes.ReplaceDisksMode)
70 $(genArbitrary ''DiskAccess)
72 instance Arbitrary OpCodes.DiskIndex where
73 arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
75 instance Arbitrary INicParams where
76 arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
77 genMaybe genNameNE <*> genMaybe genNameNE <*> genMaybe genNameNE
79 instance Arbitrary IDiskParams where
80 arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
81 genMaybe genNameNE <*> genMaybe genNameNE <*>
82 genMaybe genNameNE <*> genMaybe genNameNE
84 instance Arbitrary RecreateDisksInfo where
85 arbitrary = oneof [ pure RecreateDisksAll
86 , RecreateDisksIndices <$> arbitrary
87 , RecreateDisksParams <$> arbitrary
90 instance Arbitrary DdmOldChanges where
91 arbitrary = oneof [ DdmOldIndex <$> arbitrary
92 , DdmOldMod <$> arbitrary
95 instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
96 arbitrary = oneof [ pure SetParamsEmpty
97 , SetParamsDeprecated <$> arbitrary
98 , SetParamsNew <$> arbitrary
101 instance Arbitrary ExportTarget where
102 arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
103 , ExportTargetRemote <$> pure []
106 instance Arbitrary OpCodes.OpCode where
108 op_id <- elements OpCodes.allOpIDs
111 OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
112 genNodeNamesNE <*> return Nothing <*> arbitrary
113 "OP_INSTANCE_REPLACE_DISKS" ->
114 OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
115 arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
116 genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
117 "OP_INSTANCE_FAILOVER" ->
118 OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
119 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
120 return Nothing <*> arbitrary <*> genMaybe genNameNE
121 "OP_INSTANCE_MIGRATE" ->
122 OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
123 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
124 return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
125 genMaybe genNameNE <*> arbitrary
127 OpCodes.OpTagsGet <$> arbitrary <*> arbitrary
129 OpCodes.OpTagsSearch <$> genNameNE
131 OpCodes.OpTagsSet <$> arbitrary <*> genTags
133 OpCodes.OpTagsSet <$> arbitrary <*> genTags
134 "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
135 "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
136 "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
137 "OP_CLUSTER_VERIFY" ->
138 OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
139 genSet Nothing <*> genSet Nothing <*> arbitrary <*>
141 "OP_CLUSTER_VERIFY_CONFIG" ->
142 OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
143 genSet Nothing <*> arbitrary
144 "OP_CLUSTER_VERIFY_GROUP" ->
145 OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
146 arbitrary <*> genSet Nothing <*> genSet Nothing <*> arbitrary
147 "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
148 "OP_GROUP_VERIFY_DISKS" ->
149 OpCodes.OpGroupVerifyDisks <$> genNameNE
150 "OP_CLUSTER_REPAIR_DISK_SIZES" ->
151 OpCodes.OpClusterRepairDiskSizes <$> genNodeNamesNE
152 "OP_CLUSTER_CONFIG_QUERY" ->
153 OpCodes.OpClusterConfigQuery <$> genFieldsNE
154 "OP_CLUSTER_RENAME" ->
155 OpCodes.OpClusterRename <$> genNameNE
156 "OP_CLUSTER_SET_PARAMS" ->
157 OpCodes.OpClusterSetParams <$> arbitrary <*> emptyMUD <*> emptyMUD <*>
158 arbitrary <*> genMaybe (listOf1 arbitrary >>= mkNonEmpty) <*>
159 genMaybe genEmptyContainer <*> emptyMUD <*>
160 genMaybe genEmptyContainer <*> genMaybe genEmptyContainer <*>
161 genMaybe genEmptyContainer <*> genMaybe arbitrary <*>
162 arbitrary <*> arbitrary <*> arbitrary <*>
163 arbitrary <*> arbitrary <*> arbitrary <*>
164 emptyMUD <*> emptyMUD <*> arbitrary <*>
165 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
166 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
167 genMaybe (genName >>= mkNonEmpty) <*>
168 genMaybe (genName >>= mkNonEmpty)
169 "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
170 "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
171 pure OpCodes.OpClusterActivateMasterIp
172 "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
173 pure OpCodes.OpClusterDeactivateMasterIp
175 OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*> genFilter
177 OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
179 OpCodes.OpOobCommand <$> genNodeNamesNE <*> return Nothing <*>
180 arbitrary <*> arbitrary <*> arbitrary <*>
181 (arbitrary `suchThat` (>0))
183 OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
185 OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
186 genMaybe genName <*> genMaybe genNameNE <*> arbitrary <*>
187 genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
189 OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
190 "OP_NODE_QUERYVOLS" ->
191 OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
192 "OP_NODE_QUERY_STORAGE" ->
193 OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
194 genNodeNamesNE <*> genNameNE
195 "OP_NODE_MODIFY_STORAGE" ->
196 OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
197 arbitrary <*> genNameNE <*> pure emptyJSObject
198 "OP_REPAIR_NODE_STORAGE" ->
199 OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
200 arbitrary <*> genNameNE <*> arbitrary
201 "OP_NODE_SET_PARAMS" ->
202 OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
203 arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
204 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
205 genMaybe genNameNE <*> emptyMUD <*> arbitrary
206 "OP_NODE_POWERCYCLE" ->
207 OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
210 OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
211 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
212 return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
213 "OP_NODE_EVACUATE" ->
214 OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
215 return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
216 genMaybe genNameNE <*> arbitrary
217 "OP_INSTANCE_CREATE" ->
218 OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
219 arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
220 arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
221 pure emptyJSObject <*> arbitrary <*> genMaybe genNameNE <*>
222 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
223 arbitrary <*> arbitrary <*> pure emptyJSObject <*>
224 genMaybe genNameNE <*>
225 genMaybe genNodeNameNE <*> return Nothing <*>
226 genMaybe genNodeNameNE <*> return Nothing <*>
227 genMaybe (pure []) <*> genMaybe genNodeNameNE <*>
228 arbitrary <*> genMaybe genNodeNameNE <*> return Nothing <*>
229 genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
230 arbitrary <*> arbitrary <*> (genTags >>= mapM mkNonEmpty)
231 "OP_INSTANCE_MULTI_ALLOC" ->
232 OpCodes.OpInstanceMultiAlloc <$> genMaybe genNameNE <*> pure [] <*>
234 "OP_INSTANCE_REINSTALL" ->
235 OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
236 arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
237 "OP_INSTANCE_REMOVE" ->
238 OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
239 arbitrary <*> arbitrary
240 "OP_INSTANCE_RENAME" ->
241 OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
242 genNodeNameNE <*> arbitrary <*> arbitrary
243 "OP_INSTANCE_STARTUP" ->
244 OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
245 arbitrary <*> arbitrary <*> pure emptyJSObject <*>
246 pure emptyJSObject <*> arbitrary <*> arbitrary
247 "OP_INSTANCE_SHUTDOWN" ->
248 OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
249 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
250 "OP_INSTANCE_REBOOT" ->
251 OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
252 arbitrary <*> arbitrary <*> arbitrary
253 "OP_INSTANCE_MOVE" ->
254 OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
255 arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
257 "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
259 "OP_INSTANCE_ACTIVATE_DISKS" ->
260 OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
261 arbitrary <*> arbitrary
262 "OP_INSTANCE_DEACTIVATE_DISKS" ->
263 OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
265 "OP_INSTANCE_RECREATE_DISKS" ->
266 OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
267 arbitrary <*> genNodeNamesNE <*> return Nothing <*>
269 "OP_INSTANCE_QUERY" ->
270 OpCodes.OpInstanceQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
271 "OP_INSTANCE_QUERY_DATA" ->
272 OpCodes.OpInstanceQueryData <$> arbitrary <*>
273 genNodeNamesNE <*> arbitrary
274 "OP_INSTANCE_SET_PARAMS" ->
275 OpCodes.OpInstanceSetParams <$> genFQDN <*> return Nothing <*>
276 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
277 arbitrary <*> pure emptyJSObject <*> arbitrary <*>
278 pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
279 return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
280 genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
281 arbitrary <*> arbitrary
282 "OP_INSTANCE_GROW_DISK" ->
283 OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
284 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
285 "OP_INSTANCE_CHANGE_GROUP" ->
286 OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
287 arbitrary <*> genMaybe genNameNE <*>
288 genMaybe (resize maxNodes (listOf genNameNE))
290 OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
291 emptyMUD <*> genMaybe genEmptyContainer <*>
292 emptyMUD <*> emptyMUD <*> emptyMUD
293 "OP_GROUP_ASSIGN_NODES" ->
294 OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
295 genNodeNamesNE <*> return Nothing
297 OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
298 "OP_GROUP_SET_PARAMS" ->
299 OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
300 emptyMUD <*> genMaybe genEmptyContainer <*>
301 emptyMUD <*> emptyMUD <*> emptyMUD
303 OpCodes.OpGroupRemove <$> genNameNE
305 OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
306 "OP_GROUP_EVACUATE" ->
307 OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
308 genMaybe genNameNE <*> genMaybe genNamesNE
310 OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
311 "OP_EXT_STORAGE_DIAGNOSE" ->
312 OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
314 OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
315 "OP_BACKUP_PREPARE" ->
316 OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
317 "OP_BACKUP_EXPORT" ->
318 OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
319 arbitrary <*> arbitrary <*> return Nothing <*> arbitrary <*>
320 arbitrary <*> arbitrary <*> arbitrary <*> genMaybe (pure []) <*>
322 "OP_BACKUP_REMOVE" ->
323 OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
324 "OP_TEST_ALLOCATOR" ->
325 OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
326 genNameNE <*> pure [] <*> pure [] <*>
327 arbitrary <*> genMaybe genNameNE <*>
328 (genTags >>= mapM mkNonEmpty) <*>
329 arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
330 arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
331 genMaybe genNamesNE <*> arbitrary <*> arbitrary
333 OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
334 resize 20 (listOf genFQDN) <*> arbitrary
336 OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
337 pure J.JSNull <*> pure J.JSNull
339 OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*>
340 genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
341 genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
342 arbitrary <*> (genTags >>= mapM mkNonEmpty)
343 "OP_NETWORK_REMOVE" ->
344 OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
345 "OP_NETWORK_SET_PARAMS" ->
346 OpCodes.OpNetworkSetParams <$> genNameNE <*>
347 genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
348 genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
349 genMaybe (listOf genIp4Addr)
350 "OP_NETWORK_CONNECT" ->
351 OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
352 arbitrary <*> genNameNE <*> arbitrary
353 "OP_NETWORK_DISCONNECT" ->
354 OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
355 "OP_NETWORK_QUERY" ->
356 OpCodes.OpNetworkQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
357 "OP_RESTRICTED_COMMAND" ->
358 OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
359 return Nothing <*> genNameNE
360 _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
362 -- | Generates one element of a reason trail
363 genReasonElem :: Gen ReasonElem
364 genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
366 -- | Generates a reason trail
367 genReasonTrail :: Gen ReasonTrail
369 size <- choose (0, 10)
370 vectorOf size genReasonElem
372 instance Arbitrary OpCodes.CommonOpParams where
373 arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
374 arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
377 -- * Helper functions
380 emptyJSObject :: J.JSObject J.JSValue
381 emptyJSObject = J.toJSObject []
383 -- | Empty maybe unchecked dictionary.
384 emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
385 emptyMUD = genMaybe $ pure emptyJSObject
387 -- | Generates an empty container.
388 genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
389 genEmptyContainer = pure . GenericContainer $ Map.fromList []
391 -- | Generates list of disk indices.
392 genDiskIndices :: Gen [DiskIndex]
394 cnt <- choose (0, C.maxDisks)
395 genUniquesList cnt arbitrary
397 -- | Generates a list of node names.
398 genNodeNames :: Gen [String]
399 genNodeNames = resize maxNodes (listOf genFQDN)
401 -- | Generates a list of node names in non-empty string type.
402 genNodeNamesNE :: Gen [NonEmptyString]
403 genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
405 -- | Gets a node name in non-empty type.
406 genNodeNameNE :: Gen NonEmptyString
407 genNodeNameNE = genFQDN >>= mkNonEmpty
409 -- | Gets a name (non-fqdn) in non-empty type.
410 genNameNE :: Gen NonEmptyString
411 genNameNE = genName >>= mkNonEmpty
413 -- | Gets a list of names (non-fqdn) in non-empty type.
414 genNamesNE :: Gen [NonEmptyString]
415 genNamesNE = resize maxNodes (listOf genNameNE)
417 -- | Returns a list of non-empty fields.
418 genFieldsNE :: Gen [NonEmptyString]
419 genFieldsNE = genFields >>= mapM mkNonEmpty
421 -- | Generate a 3-byte MAC prefix.
422 genMacPrefix :: Gen NonEmptyString
424 octets <- vectorOf 3 $ choose (0::Int, 255)
425 mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
427 -- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
428 $(genArbitrary ''OpCodes.MetaOpCode)
430 -- | Small helper to check for a failed JSON deserialisation
431 isJsonError :: J.Result a -> Bool
432 isJsonError (J.Error _) = True
433 isJsonError _ = False
437 -- | Check that opcode serialization is idempotent.
438 prop_serialization :: OpCodes.OpCode -> Property
439 prop_serialization = testSerialisation
441 -- | Check that Python and Haskell defined the same opcode list.
442 case_AllDefined :: HUnit.Assertion
444 let py_ops = sort C.opcodesOpIds
445 hs_ops = sort OpCodes.allOpIDs
446 extra_py = py_ops \\ hs_ops
447 extra_hs = hs_ops \\ py_ops
448 HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
449 unlines extra_py) (null extra_py)
450 HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
451 unlines extra_hs) (null extra_hs)
453 -- | Custom HUnit test case that forks a Python process and checks
454 -- correspondence between Haskell-generated OpCodes and their Python
455 -- decoded, validated and re-encoded version.
457 -- Note that we have a strange beast here: since launching Python is
458 -- expensive, we don't do this via a usual QuickProperty, since that's
459 -- slow (I've tested it, and it's indeed quite slow). Rather, we use a
460 -- single HUnit assertion, and in it we manually use QuickCheck to
461 -- generate 500 opcodes times the number of defined opcodes, which
462 -- then we pass in bulk to Python. The drawbacks to this method are
463 -- two fold: we cannot control the number of generated opcodes, since
464 -- HUnit assertions don't get access to the test options, and for the
465 -- same reason we can't run a repeatable seed. We should probably find
466 -- a better way to do this, for example by having a
467 -- separately-launched Python process (if not running the tests would
469 case_py_compat_types :: HUnit.Assertion
470 case_py_compat_types = do
471 let num_opcodes = length OpCodes.allOpIDs * 100
472 opcodes <- genSample (vectorOf num_opcodes
473 (arbitrary::Gen OpCodes.MetaOpCode))
474 let with_sum = map (\o -> (OpCodes.opSummary $
475 OpCodes.metaOpCode o, o)) opcodes
476 serialized = J.encode opcodes
477 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
478 mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
479 HUnit.assertFailure $
480 "OpCode has non-ASCII fields: " ++ show op
483 runPython "from ganeti import opcodes\n\
485 \from ganeti import serializer\n\
486 \op_data = serializer.Load(sys.stdin.read())\n\
487 \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
488 \for op in decoded:\n\
489 \ op.Validate(True)\n\
490 \encoded = [(op.Summary(), op.__getstate__())\n\
491 \ for op in decoded]\n\
492 \print serializer.Dump(encoded)" serialized
493 >>= checkPythonResult
495 J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
496 decoded <- case deserialised of
497 J.Ok ops -> return ops
499 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
500 -- this already raised an expection, but we need it
502 >> fail "Unable to decode opcodes"
503 HUnit.assertEqual "Mismatch in number of returned opcodes"
504 (length decoded) (length with_sum)
505 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
506 ) $ zip decoded with_sum
508 -- | Custom HUnit test case that forks a Python process and checks
509 -- correspondence between Haskell OpCodes fields and their Python
511 case_py_compat_fields :: HUnit.Assertion
512 case_py_compat_fields = do
513 let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
516 runPython "from ganeti import opcodes\n\
518 \from ganeti import serializer\n\
519 \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
520 \ for k, v in opcodes.OP_MAPPING.items()]\n\
521 \print serializer.Dump(fields)" ""
522 >>= checkPythonResult
523 let deserialised = J.decode py_stdout::J.Result [(String, [String])]
524 py_fields <- case deserialised of
525 J.Ok v -> return $ sort v
527 HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
528 -- this already raised an expection, but we need it
530 >> fail "Unable to decode op fields"
531 HUnit.assertEqual "Mismatch in number of returned opcodes"
532 (length hs_fields) (length py_fields)
533 HUnit.assertEqual "Mismatch in defined OP_IDs"
534 (map fst hs_fields) (map fst py_fields)
535 mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
536 HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
537 HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
539 ) $ zip py_fields hs_fields
541 -- | Checks that setOpComment works correctly.
542 prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
543 prop_setOpComment op comment =
544 let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
545 in OpCodes.opComment common ==? Just comment
547 -- | Tests wrong tag object building (cluster takes only jsnull, the
548 -- other take a string, so we test the opposites).
549 case_TagObject_fail :: Assertion
550 case_TagObject_fail =
551 mapM_ (\(t, j) -> assertEqual (show t ++ "/" ++ J.encode j) Nothing $
553 [ (TagTypeCluster, J.showJSON "abc")
554 , (TagTypeInstance, J.JSNull)
555 , (TagTypeNode, J.JSNull)
556 , (TagTypeGroup, J.JSNull)
559 -- | Tests wrong (negative) disk index.
560 prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
561 prop_mkDiskIndex_fail (Positive i) =
562 case mkDiskIndex (negate i) of
563 Bad msg -> printTestCase "error message " $
564 "Invalid value" `isPrefixOf` msg
565 Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
566 "' from negative value " ++ show (negate i)
568 -- | Tests a few invalid 'readRecreateDisks' cases.
569 case_readRecreateDisks_fail :: Assertion
570 case_readRecreateDisks_fail = do
572 isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
573 assertBool "string" $
574 isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
576 -- | Tests a few invalid 'readDdmOldChanges' cases.
577 case_readDdmOldChanges_fail :: Assertion
578 case_readDdmOldChanges_fail = do
580 isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
581 assertBool "string" $
582 isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
584 -- | Tests a few invalid 'readExportTarget' cases.
585 case_readExportTarget_fail :: Assertion
586 case_readExportTarget_fail = do
588 isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
590 isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
593 [ 'prop_serialization
595 , 'case_py_compat_types
596 , 'case_py_compat_fields
598 , 'case_TagObject_fail
599 , 'prop_mkDiskIndex_fail
600 , 'case_readRecreateDisks_fail
601 , 'case_readDdmOldChanges_fail
602 , 'case_readExportTarget_fail