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 arbitraryOpTagsGet :: Gen OpCodes.OpCode
69 arbitraryOpTagsGet = do
71 OpCodes.OpTagsSet kind <$> arbitrary <*> genOpCodesTagName kind
73 arbitraryOpTagsSet :: Gen OpCodes.OpCode
74 arbitraryOpTagsSet = do
76 OpCodes.OpTagsSet kind <$> genTags <*> genOpCodesTagName kind
78 arbitraryOpTagsDel :: Gen OpCodes.OpCode
79 arbitraryOpTagsDel = do
81 OpCodes.OpTagsDel kind <$> genTags <*> genOpCodesTagName kind
83 $(genArbitrary ''OpCodes.ReplaceDisksMode)
85 $(genArbitrary ''DiskAccess)
87 instance Arbitrary OpCodes.DiskIndex where
88 arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
90 instance Arbitrary INicParams where
91 arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
92 genMaybe genNameNE <*> genMaybe genNameNE <*>
93 genMaybe genNameNE <*> genMaybe genNameNE <*>
96 instance Arbitrary IDiskParams where
97 arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
98 genMaybe genNameNE <*> genMaybe genNameNE <*>
99 genMaybe genNameNE <*> genMaybe genNameNE
101 instance Arbitrary RecreateDisksInfo where
102 arbitrary = oneof [ pure RecreateDisksAll
103 , RecreateDisksIndices <$> arbitrary
104 , RecreateDisksParams <$> arbitrary
107 instance Arbitrary DdmOldChanges where
108 arbitrary = oneof [ DdmOldIndex <$> arbitrary
109 , DdmOldMod <$> arbitrary
112 instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
113 arbitrary = oneof [ pure SetParamsEmpty
114 , SetParamsDeprecated <$> arbitrary
115 , SetParamsNew <$> arbitrary
118 instance Arbitrary ExportTarget where
119 arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
120 , ExportTargetRemote <$> pure []
123 instance Arbitrary OpCodes.OpCode where
125 op_id <- elements OpCodes.allOpIDs
128 OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
129 genNodeNamesNE <*> return Nothing <*> arbitrary
130 "OP_INSTANCE_REPLACE_DISKS" ->
131 OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
132 arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
133 genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
134 "OP_INSTANCE_FAILOVER" ->
135 OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
136 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
137 return Nothing <*> arbitrary <*> genMaybe genNameNE
138 "OP_INSTANCE_MIGRATE" ->
139 OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
140 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
141 return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
142 genMaybe genNameNE <*> arbitrary
146 OpCodes.OpTagsSearch <$> genNameNE
151 "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
152 "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
153 "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
154 "OP_CLUSTER_VERIFY" ->
155 OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
156 genSet Nothing <*> genSet Nothing <*> arbitrary <*>
158 "OP_CLUSTER_VERIFY_CONFIG" ->
159 OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
160 genSet Nothing <*> arbitrary
161 "OP_CLUSTER_VERIFY_GROUP" ->
162 OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
163 arbitrary <*> genSet Nothing <*> genSet Nothing <*> arbitrary
164 "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
165 "OP_GROUP_VERIFY_DISKS" ->
166 OpCodes.OpGroupVerifyDisks <$> genNameNE
167 "OP_CLUSTER_REPAIR_DISK_SIZES" ->
168 OpCodes.OpClusterRepairDiskSizes <$> genNodeNamesNE
169 "OP_CLUSTER_CONFIG_QUERY" ->
170 OpCodes.OpClusterConfigQuery <$> genFieldsNE
171 "OP_CLUSTER_RENAME" ->
172 OpCodes.OpClusterRename <$> genNameNE
173 "OP_CLUSTER_SET_PARAMS" ->
174 OpCodes.OpClusterSetParams <$> arbitrary <*> emptyMUD <*> emptyMUD <*>
175 arbitrary <*> genMaybe arbitrary <*>
176 genMaybe genEmptyContainer <*> emptyMUD <*>
177 genMaybe genEmptyContainer <*> genMaybe genEmptyContainer <*>
178 genMaybe genEmptyContainer <*> genMaybe arbitrary <*>
179 arbitrary <*> arbitrary <*> arbitrary <*>
180 arbitrary <*> arbitrary <*> arbitrary <*>
181 emptyMUD <*> emptyMUD <*> arbitrary <*>
182 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
183 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
184 genMaybe (genName >>= mkNonEmpty)
185 "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
186 "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
187 pure OpCodes.OpClusterActivateMasterIp
188 "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
189 pure OpCodes.OpClusterDeactivateMasterIp
191 OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*>
194 OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
196 OpCodes.OpOobCommand <$> genNodeNamesNE <*> return Nothing <*>
197 arbitrary <*> arbitrary <*> arbitrary <*>
198 (arbitrary `suchThat` (>0))
200 OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
202 OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
203 genNameNE <*> genMaybe genNameNE <*> arbitrary <*>
204 genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
206 OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
207 "OP_NODE_QUERYVOLS" ->
208 OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
209 "OP_NODE_QUERY_STORAGE" ->
210 OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
211 genNodeNamesNE <*> genMaybe genNameNE
212 "OP_NODE_MODIFY_STORAGE" ->
213 OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
214 arbitrary <*> genMaybe genNameNE <*> pure emptyJSObject
215 "OP_REPAIR_NODE_STORAGE" ->
216 OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
217 arbitrary <*> genMaybe genNameNE <*> arbitrary
218 "OP_NODE_SET_PARAMS" ->
219 OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
220 arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
221 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
222 genMaybe genNameNE <*> emptyMUD <*> arbitrary
223 "OP_NODE_POWERCYCLE" ->
224 OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
227 OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
228 arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
229 return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
230 "OP_NODE_EVACUATE" ->
231 OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
232 return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
233 genMaybe genNameNE <*> arbitrary
234 "OP_INSTANCE_CREATE" ->
235 OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
236 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
237 pure emptyJSObject <*> arbitrary <*> arbitrary <*> arbitrary <*>
238 genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
239 genMaybe genNameNE <*> arbitrary <*> arbitrary <*> arbitrary <*>
240 arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
241 genMaybe genNameNE <*> genMaybe genNodeNameNE <*> return Nothing <*>
242 genMaybe genNodeNameNE <*> return Nothing <*> genMaybe (pure []) <*>
243 genMaybe genNodeNameNE <*> arbitrary <*> genMaybe genNodeNameNE <*>
244 return Nothing <*> genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
245 arbitrary <*> (genTags >>= mapM mkNonEmpty)
246 "OP_INSTANCE_MULTI_ALLOC" ->
247 OpCodes.OpInstanceMultiAlloc <$> arbitrary <*> genMaybe genNameNE <*>
249 "OP_INSTANCE_REINSTALL" ->
250 OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
251 arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
252 "OP_INSTANCE_REMOVE" ->
253 OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
254 arbitrary <*> arbitrary
255 "OP_INSTANCE_RENAME" ->
256 OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
257 genNodeNameNE <*> arbitrary <*> arbitrary
258 "OP_INSTANCE_STARTUP" ->
259 OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
260 arbitrary <*> arbitrary <*> pure emptyJSObject <*>
261 pure emptyJSObject <*> arbitrary <*> arbitrary
262 "OP_INSTANCE_SHUTDOWN" ->
263 OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
264 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
265 "OP_INSTANCE_REBOOT" ->
266 OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
267 arbitrary <*> arbitrary <*> arbitrary
268 "OP_INSTANCE_MOVE" ->
269 OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
270 arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
272 "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
274 "OP_INSTANCE_ACTIVATE_DISKS" ->
275 OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
276 arbitrary <*> arbitrary
277 "OP_INSTANCE_DEACTIVATE_DISKS" ->
278 OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
280 "OP_INSTANCE_RECREATE_DISKS" ->
281 OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
282 arbitrary <*> genNodeNamesNE <*> return Nothing <*>
284 "OP_INSTANCE_QUERY" ->
285 OpCodes.OpInstanceQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
286 "OP_INSTANCE_QUERY_DATA" ->
287 OpCodes.OpInstanceQueryData <$> arbitrary <*>
288 genNodeNamesNE <*> arbitrary
289 "OP_INSTANCE_SET_PARAMS" ->
290 OpCodes.OpInstanceSetParams <$> genFQDN <*> return Nothing <*>
291 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
292 arbitrary <*> pure emptyJSObject <*> arbitrary <*>
293 pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
294 return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
295 genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
296 arbitrary <*> arbitrary
297 "OP_INSTANCE_GROW_DISK" ->
298 OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
299 arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
300 "OP_INSTANCE_CHANGE_GROUP" ->
301 OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
302 arbitrary <*> genMaybe genNameNE <*>
303 genMaybe (resize maxNodes (listOf genNameNE))
305 OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
306 emptyMUD <*> genMaybe genEmptyContainer <*>
307 emptyMUD <*> emptyMUD <*> emptyMUD
308 "OP_GROUP_ASSIGN_NODES" ->
309 OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
310 genNodeNamesNE <*> return Nothing
312 OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
313 "OP_GROUP_SET_PARAMS" ->
314 OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
315 emptyMUD <*> genMaybe genEmptyContainer <*>
316 emptyMUD <*> emptyMUD <*> emptyMUD
318 OpCodes.OpGroupRemove <$> genNameNE
320 OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
321 "OP_GROUP_EVACUATE" ->
322 OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
323 genMaybe genNameNE <*> genMaybe genNamesNE
325 OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
326 "OP_EXT_STORAGE_DIAGNOSE" ->
327 OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
329 OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
330 "OP_BACKUP_PREPARE" ->
331 OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
332 "OP_BACKUP_EXPORT" ->
333 OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
334 arbitrary <*> arbitrary <*> return Nothing <*> arbitrary <*>
335 arbitrary <*> arbitrary <*> arbitrary <*> genMaybe (pure []) <*>
337 "OP_BACKUP_REMOVE" ->
338 OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
339 "OP_TEST_ALLOCATOR" ->
340 OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
341 genNameNE <*> genMaybe (pure []) <*> genMaybe (pure []) <*>
342 arbitrary <*> genMaybe genNameNE <*>
343 (genTags >>= mapM mkNonEmpty) <*>
344 arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
345 arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
346 genMaybe genNamesNE <*> arbitrary <*> arbitrary
348 OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
349 resize 20 (listOf genFQDN) <*> arbitrary
351 OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
352 pure J.JSNull <*> pure J.JSNull
354 OpCodes.OpNetworkAdd <$> genNameNE <*> genIPv4Network <*>
355 genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
356 genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
357 arbitrary <*> (genTags >>= mapM mkNonEmpty)
358 "OP_NETWORK_REMOVE" ->
359 OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
360 "OP_NETWORK_SET_PARAMS" ->
361 OpCodes.OpNetworkSetParams <$> genNameNE <*>
362 genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
363 genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
364 genMaybe (listOf genIPv4Address)
365 "OP_NETWORK_CONNECT" ->
366 OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
367 arbitrary <*> genNameNE <*> arbitrary
368 "OP_NETWORK_DISCONNECT" ->
369 OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
370 "OP_NETWORK_QUERY" ->
371 OpCodes.OpNetworkQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
372 "OP_RESTRICTED_COMMAND" ->
373 OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
374 return Nothing <*> genNameNE
375 _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
377 -- | Generates one element of a reason trail
378 genReasonElem :: Gen ReasonElem
379 genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
381 -- | Generates a reason trail
382 genReasonTrail :: Gen ReasonTrail
384 size <- choose (0, 10)
385 vectorOf size genReasonElem
387 instance Arbitrary OpCodes.CommonOpParams where
388 arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
389 arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
392 -- * Helper functions
395 emptyJSObject :: J.JSObject J.JSValue
396 emptyJSObject = J.toJSObject []
398 -- | Empty maybe unchecked dictionary.
399 emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
400 emptyMUD = genMaybe $ pure emptyJSObject
402 -- | Generates an empty container.
403 genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
404 genEmptyContainer = pure . GenericContainer $ Map.fromList []
406 -- | Generates list of disk indices.
407 genDiskIndices :: Gen [DiskIndex]
409 cnt <- choose (0, C.maxDisks)
410 genUniquesList cnt arbitrary
412 -- | Generates a list of node names.
413 genNodeNames :: Gen [String]
414 genNodeNames = resize maxNodes (listOf genFQDN)
416 -- | Generates a list of node names in non-empty string type.
417 genNodeNamesNE :: Gen [NonEmptyString]
418 genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
420 -- | Gets a node name in non-empty type.
421 genNodeNameNE :: Gen NonEmptyString
422 genNodeNameNE = genFQDN >>= mkNonEmpty
424 -- | Gets a name (non-fqdn) in non-empty type.
425 genNameNE :: Gen NonEmptyString
426 genNameNE = genName >>= mkNonEmpty
428 -- | Gets a list of names (non-fqdn) in non-empty type.
429 genNamesNE :: Gen [NonEmptyString]
430 genNamesNE = resize maxNodes (listOf genNameNE)
432 -- | Returns a list of non-empty fields.
433 genFieldsNE :: Gen [NonEmptyString]
434 genFieldsNE = genFields >>= mapM mkNonEmpty
436 -- | Generate a 3-byte MAC prefix.
437 genMacPrefix :: Gen NonEmptyString
439 octets <- vectorOf 3 $ choose (0::Int, 255)
440 mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
442 -- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
443 $(genArbitrary ''OpCodes.MetaOpCode)
445 -- | Small helper to check for a failed JSON deserialisation
446 isJsonError :: J.Result a -> Bool
447 isJsonError (J.Error _) = True
448 isJsonError _ = False
452 -- | Check that opcode serialization is idempotent.
453 prop_serialization :: OpCodes.OpCode -> Property
454 prop_serialization = testSerialisation
456 -- | Check that Python and Haskell defined the same opcode list.
457 case_AllDefined :: HUnit.Assertion
460 runPython "from ganeti import opcodes\n\
461 \from ganeti import serializer\n\
463 \print serializer.Dump([opid for opid in opcodes.OP_MAPPING])\n"
465 >>= checkPythonResult
466 py_ops <- case J.decode py_stdout::J.Result [String] of
467 J.Ok ops -> return ops
469 HUnit.assertFailure ("Unable to decode opcode names: " ++ msg)
470 -- this already raised an expection, but we need it
472 >> fail "Unable to decode opcode names"
473 let hs_ops = sort OpCodes.allOpIDs
474 extra_py = py_ops \\ hs_ops
475 extra_hs = hs_ops \\ py_ops
476 HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
477 unlines extra_py) (null extra_py)
478 HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
479 unlines extra_hs) (null extra_hs)
481 -- | Custom HUnit test case that forks a Python process and checks
482 -- correspondence between Haskell-generated OpCodes and their Python
483 -- decoded, validated and re-encoded version.
485 -- Note that we have a strange beast here: since launching Python is
486 -- expensive, we don't do this via a usual QuickProperty, since that's
487 -- slow (I've tested it, and it's indeed quite slow). Rather, we use a
488 -- single HUnit assertion, and in it we manually use QuickCheck to
489 -- generate 500 opcodes times the number of defined opcodes, which
490 -- then we pass in bulk to Python. The drawbacks to this method are
491 -- two fold: we cannot control the number of generated opcodes, since
492 -- HUnit assertions don't get access to the test options, and for the
493 -- same reason we can't run a repeatable seed. We should probably find
494 -- a better way to do this, for example by having a
495 -- separately-launched Python process (if not running the tests would
497 case_py_compat_types :: HUnit.Assertion
498 case_py_compat_types = do
499 let num_opcodes = length OpCodes.allOpIDs * 100
500 opcodes <- genSample (vectorOf num_opcodes
501 (arbitrary::Gen OpCodes.MetaOpCode))
502 let with_sum = map (\o -> (OpCodes.opSummary $
503 OpCodes.metaOpCode o, o)) opcodes
504 serialized = J.encode opcodes
505 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
506 mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
507 HUnit.assertFailure $
508 "OpCode has non-ASCII fields: " ++ show op
511 runPython "from ganeti import opcodes\n\
512 \from ganeti import serializer\n\
514 \op_data = serializer.Load(sys.stdin.read())\n\
515 \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
516 \for op in decoded:\n\
517 \ op.Validate(True)\n\
518 \encoded = [(op.Summary(), op.__getstate__())\n\
519 \ for op in decoded]\n\
520 \print serializer.Dump(encoded)" serialized
521 >>= checkPythonResult
523 J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
524 decoded <- case deserialised of
525 J.Ok ops -> return ops
527 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
528 -- this already raised an expection, but we need it
530 >> fail "Unable to decode opcodes"
531 HUnit.assertEqual "Mismatch in number of returned opcodes"
532 (length decoded) (length with_sum)
533 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
534 ) $ zip decoded with_sum
536 -- | Custom HUnit test case that forks a Python process and checks
537 -- correspondence between Haskell OpCodes fields and their Python
539 case_py_compat_fields :: HUnit.Assertion
540 case_py_compat_fields = do
541 let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
544 runPython "from ganeti import opcodes\n\
546 \from ganeti import serializer\n\
547 \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
548 \ for k, v in opcodes.OP_MAPPING.items()]\n\
549 \print serializer.Dump(fields)" ""
550 >>= checkPythonResult
551 let deserialised = J.decode py_stdout::J.Result [(String, [String])]
552 py_fields <- case deserialised of
553 J.Ok v -> return $ sort v
555 HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
556 -- this already raised an expection, but we need it
558 >> fail "Unable to decode op fields"
559 HUnit.assertEqual "Mismatch in number of returned opcodes"
560 (length hs_fields) (length py_fields)
561 HUnit.assertEqual "Mismatch in defined OP_IDs"
562 (map fst hs_fields) (map fst py_fields)
563 mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
564 HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
565 HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
567 ) $ zip py_fields hs_fields
569 -- | Checks that setOpComment works correctly.
570 prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
571 prop_setOpComment op comment =
572 let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
573 in OpCodes.opComment common ==? Just comment
575 -- | Tests wrong (negative) disk index.
576 prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
577 prop_mkDiskIndex_fail (Positive i) =
578 case mkDiskIndex (negate i) of
579 Bad msg -> printTestCase "error message " $
580 "Invalid value" `isPrefixOf` msg
581 Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
582 "' from negative value " ++ show (negate i)
584 -- | Tests a few invalid 'readRecreateDisks' cases.
585 case_readRecreateDisks_fail :: Assertion
586 case_readRecreateDisks_fail = do
588 isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
589 assertBool "string" $
590 isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
592 -- | Tests a few invalid 'readDdmOldChanges' cases.
593 case_readDdmOldChanges_fail :: Assertion
594 case_readDdmOldChanges_fail = do
596 isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
597 assertBool "string" $
598 isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
600 -- | Tests a few invalid 'readExportTarget' cases.
601 case_readExportTarget_fail :: Assertion
602 case_readExportTarget_fail = do
604 isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
606 isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
609 [ 'prop_serialization
611 , 'case_py_compat_types
612 , 'case_py_compat_fields
614 , 'prop_mkDiskIndex_fail
615 , 'case_readRecreateDisks_fail
616 , 'case_readDdmOldChanges_fail
617 , 'case_readExportTarget_fail