Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ 1c88fa29

History | View | Annotate | Download (25.9 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2
{-# OPTIONS_GHC -fno-warn-orphans #-}
3

    
4
{-| Unittests for ganeti-htools.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
11

    
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.
16

    
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.
21

    
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
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Test.Ganeti.OpCodes
30
  ( testOpCodes
31
  , OpCodes.OpCode(..)
32
  ) where
33

    
34
import Test.HUnit as HUnit
35
import Test.QuickCheck as QuickCheck
36

    
37
import Control.Applicative
38
import Control.Monad
39
import Data.Char
40
import Data.List
41
import qualified Data.Map as Map
42
import qualified Text.JSON as J
43
import Text.Printf (printf)
44

    
45
import Test.Ganeti.TestHelper
46
import Test.Ganeti.TestCommon
47
import Test.Ganeti.Types ()
48
import Test.Ganeti.Query.Language ()
49

    
50
import Ganeti.BasicTypes
51
import qualified Ganeti.Constants as C
52
import qualified Ganeti.OpCodes as OpCodes
53
import Ganeti.Types
54
import Ganeti.OpParams
55
import Ganeti.JSON
56

    
57
{-# ANN module "HLint: ignore Use camelCase" #-}
58

    
59
-- * Arbitrary instances
60

    
61
instance (Ord k, Arbitrary k, Arbitrary a) => Arbitrary (Map.Map k a) where
62
  arbitrary = Map.fromList <$> arbitrary
63

    
64
arbitraryOpTagsGet :: Gen OpCodes.OpCode
65
arbitraryOpTagsGet = do
66
  kind <- arbitrary
67
  OpCodes.OpTagsSet kind <$> arbitrary <*> genOpCodesTagName kind
68

    
69
arbitraryOpTagsSet :: Gen OpCodes.OpCode
70
arbitraryOpTagsSet = do
71
  kind <- arbitrary
72
  OpCodes.OpTagsSet kind <$> genTags <*> genOpCodesTagName kind
73

    
74
arbitraryOpTagsDel :: Gen OpCodes.OpCode
75
arbitraryOpTagsDel = do
76
  kind <- arbitrary
77
  OpCodes.OpTagsDel kind <$> genTags <*> genOpCodesTagName kind
78

    
79
$(genArbitrary ''OpCodes.ReplaceDisksMode)
80

    
81
$(genArbitrary ''DiskAccess)
82

    
83
instance Arbitrary OpCodes.DiskIndex where
84
  arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
85

    
86
instance Arbitrary INicParams where
87
  arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
88
              genMaybe genNameNE <*> genMaybe genNameNE <*>
89
              genMaybe genNameNE <*> genMaybe genNameNE <*>
90
              genMaybe genNameNE
91

    
92
instance Arbitrary IDiskParams where
93
  arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
94
              genMaybe genNameNE <*> genMaybe genNameNE <*>
95
              genMaybe genNameNE <*> genMaybe genNameNE <*>
96
              genMaybe genNameNE <*> genMaybe genNameNE <*> genAndRestArguments
97

    
98
instance Arbitrary RecreateDisksInfo where
99
  arbitrary = oneof [ pure RecreateDisksAll
100
                    , RecreateDisksIndices <$> arbitrary
101
                    , RecreateDisksParams <$> arbitrary
102
                    ]
103

    
104
instance Arbitrary DdmOldChanges where
105
  arbitrary = oneof [ DdmOldIndex <$> arbitrary
106
                    , DdmOldMod   <$> arbitrary
107
                    ]
108

    
109
instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
110
  arbitrary = oneof [ pure SetParamsEmpty
111
                    , SetParamsDeprecated <$> arbitrary
112
                    , SetParamsNew        <$> arbitrary
113
                    ]
114

    
115
instance Arbitrary ExportTarget where
116
  arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
117
                    , ExportTargetRemote <$> pure []
118
                    ]
119

    
120
instance Arbitrary OpCodes.OpCode where
121
  arbitrary = do
122
    op_id <- elements OpCodes.allOpIDs
123
    case op_id of
124
      "OP_TEST_DELAY" ->
125
        OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
126
          genNodeNamesNE <*> return Nothing <*> arbitrary <*> arbitrary
127
      "OP_INSTANCE_REPLACE_DISKS" ->
128
        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
129
          arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
130
          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
131
      "OP_INSTANCE_FAILOVER" ->
132
        OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
133
        arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
134
        return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
135
      "OP_INSTANCE_MIGRATE" ->
136
        OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
137
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
138
          return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
139
          genMaybe genNameNE <*> arbitrary
140
      "OP_TAGS_GET" ->
141
        arbitraryOpTagsGet
142
      "OP_TAGS_SEARCH" ->
143
        OpCodes.OpTagsSearch <$> genNameNE
144
      "OP_TAGS_SET" ->
145
        arbitraryOpTagsSet
146
      "OP_TAGS_DEL" ->
147
        arbitraryOpTagsDel
148
      "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
149
      "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
150
      "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
151
      "OP_CLUSTER_VERIFY" ->
152
        OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
153
          genListSet Nothing <*> genListSet Nothing <*> arbitrary <*>
154
          genMaybe genNameNE
155
      "OP_CLUSTER_VERIFY_CONFIG" ->
156
        OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
157
          genListSet Nothing <*> arbitrary
158
      "OP_CLUSTER_VERIFY_GROUP" ->
159
        OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
160
          arbitrary <*> genListSet Nothing <*> genListSet Nothing <*> arbitrary
161
      "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
162
      "OP_GROUP_VERIFY_DISKS" ->
163
        OpCodes.OpGroupVerifyDisks <$> genNameNE
164
      "OP_CLUSTER_REPAIR_DISK_SIZES" ->
165
        OpCodes.OpClusterRepairDiskSizes <$> genNodeNamesNE
166
      "OP_CLUSTER_CONFIG_QUERY" ->
167
        OpCodes.OpClusterConfigQuery <$> genFieldsNE
168
      "OP_CLUSTER_RENAME" ->
169
        OpCodes.OpClusterRename <$> genNameNE
170
      "OP_CLUSTER_SET_PARAMS" ->
171
        OpCodes.OpClusterSetParams <$> arbitrary <*> emptyMUD <*> emptyMUD <*>
172
          arbitrary <*> genMaybe arbitrary <*>
173
          genMaybe genEmptyContainer <*> emptyMUD <*>
174
          genMaybe genEmptyContainer <*> genMaybe genEmptyContainer <*>
175
          genMaybe genEmptyContainer <*> genMaybe arbitrary <*>
176
          arbitrary <*> arbitrary <*> arbitrary <*>
177
          arbitrary <*> arbitrary <*> arbitrary <*>
178
          emptyMUD <*> emptyMUD <*> arbitrary <*>
179
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
180
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
181
          genMaybe genName <*>
182
          genMaybe genName
183
      "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
184
      "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
185
        pure OpCodes.OpClusterActivateMasterIp
186
      "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
187
        pure OpCodes.OpClusterDeactivateMasterIp
188
      "OP_QUERY" ->
189
        OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*>
190
        pure Nothing
191
      "OP_QUERY_FIELDS" ->
192
        OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
193
      "OP_OOB_COMMAND" ->
194
        OpCodes.OpOobCommand <$> genNodeNamesNE <*> return Nothing <*>
195
          arbitrary <*> arbitrary <*> arbitrary <*>
196
          (arbitrary `suchThat` (>0))
197
      "OP_NODE_REMOVE" ->
198
        OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
199
      "OP_NODE_ADD" ->
200
        OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
201
          genMaybe genNameNE <*> genMaybe genNameNE <*> arbitrary <*>
202
          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
203
      "OP_NODE_QUERY" ->
204
        OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
205
      "OP_NODE_QUERYVOLS" ->
206
        OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
207
      "OP_NODE_QUERY_STORAGE" ->
208
        OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
209
          genNodeNamesNE <*> genMaybe genNameNE
210
      "OP_NODE_MODIFY_STORAGE" ->
211
        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
212
          arbitrary <*> genMaybe genNameNE <*> pure emptyJSObject
213
      "OP_REPAIR_NODE_STORAGE" ->
214
        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
215
          arbitrary <*> genMaybe genNameNE <*> arbitrary
216
      "OP_NODE_SET_PARAMS" ->
217
        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
218
          arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
219
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
220
          genMaybe genNameNE <*> emptyMUD <*> arbitrary
221
      "OP_NODE_POWERCYCLE" ->
222
        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
223
          arbitrary
224
      "OP_NODE_MIGRATE" ->
225
        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
226
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
227
          return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
228
      "OP_NODE_EVACUATE" ->
229
        OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
230
          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
231
          genMaybe genNameNE <*> arbitrary
232
      "OP_INSTANCE_CREATE" ->
233
        OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
234
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
235
          pure emptyJSObject <*> arbitrary <*> arbitrary <*> arbitrary <*>
236
          genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
237
          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> arbitrary <*>
238
          arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
239
          genMaybe genNameNE <*> genMaybe genNodeNameNE <*> return Nothing <*>
240
          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe (pure []) <*>
241
          genMaybe genNodeNameNE <*> arbitrary <*> genMaybe genNodeNameNE <*>
242
          return Nothing <*> genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
243
          arbitrary <*> (genTags >>= mapM mkNonEmpty)
244
      "OP_INSTANCE_MULTI_ALLOC" ->
245
        OpCodes.OpInstanceMultiAlloc <$> arbitrary <*> genMaybe genNameNE <*>
246
        pure []
247
      "OP_INSTANCE_REINSTALL" ->
248
        OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
249
          arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
250
      "OP_INSTANCE_REMOVE" ->
251
        OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
252
          arbitrary <*> arbitrary
253
      "OP_INSTANCE_RENAME" ->
254
        OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
255
          genNodeNameNE <*> arbitrary <*> arbitrary
256
      "OP_INSTANCE_STARTUP" ->
257
        OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
258
          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
259
          pure emptyJSObject <*> arbitrary <*> arbitrary
260
      "OP_INSTANCE_SHUTDOWN" ->
261
        OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
262
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
263
      "OP_INSTANCE_REBOOT" ->
264
        OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
265
          arbitrary <*> arbitrary <*> arbitrary
266
      "OP_INSTANCE_MOVE" ->
267
        OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
268
          arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
269
          arbitrary
270
      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
271
          return Nothing
272
      "OP_INSTANCE_ACTIVATE_DISKS" ->
273
        OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
274
          arbitrary <*> arbitrary
275
      "OP_INSTANCE_DEACTIVATE_DISKS" ->
276
        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
277
          arbitrary
278
      "OP_INSTANCE_RECREATE_DISKS" ->
279
        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
280
          arbitrary <*> genNodeNamesNE <*> return Nothing <*>
281
          genMaybe genNameNE
282
      "OP_INSTANCE_QUERY" ->
283
        OpCodes.OpInstanceQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
284
      "OP_INSTANCE_QUERY_DATA" ->
285
        OpCodes.OpInstanceQueryData <$> arbitrary <*>
286
          genNodeNamesNE <*> arbitrary
287
      "OP_INSTANCE_SET_PARAMS" ->
288
        OpCodes.OpInstanceSetParams <$> genFQDN <*> return Nothing <*>
289
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
290
          arbitrary <*> pure emptyJSObject <*> arbitrary <*>
291
          pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
292
          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
293
          genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
294
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
295
      "OP_INSTANCE_GROW_DISK" ->
296
        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
297
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
298
      "OP_INSTANCE_CHANGE_GROUP" ->
299
        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
300
          arbitrary <*> genMaybe genNameNE <*>
301
          genMaybe (resize maxNodes (listOf genNameNE))
302
      "OP_GROUP_ADD" ->
303
        OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
304
          emptyMUD <*> genMaybe genEmptyContainer <*>
305
          emptyMUD <*> emptyMUD <*> emptyMUD
306
      "OP_GROUP_ASSIGN_NODES" ->
307
        OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
308
          genNodeNamesNE <*> return Nothing
309
      "OP_GROUP_QUERY" ->
310
        OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
311
      "OP_GROUP_SET_PARAMS" ->
312
        OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
313
          emptyMUD <*> genMaybe genEmptyContainer <*>
314
          emptyMUD <*> emptyMUD <*> emptyMUD
315
      "OP_GROUP_REMOVE" ->
316
        OpCodes.OpGroupRemove <$> genNameNE
317
      "OP_GROUP_RENAME" ->
318
        OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
319
      "OP_GROUP_EVACUATE" ->
320
        OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
321
          genMaybe genNameNE <*> genMaybe genNamesNE <*> arbitrary <*> arbitrary
322
      "OP_OS_DIAGNOSE" ->
323
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
324
      "OP_EXT_STORAGE_DIAGNOSE" ->
325
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
326
      "OP_BACKUP_QUERY" ->
327
        OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
328
      "OP_BACKUP_PREPARE" ->
329
        OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
330
      "OP_BACKUP_EXPORT" ->
331
        OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
332
          arbitrary <*> arbitrary <*> return Nothing <*> arbitrary <*>
333
          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe (pure []) <*>
334
          genMaybe genNameNE
335
      "OP_BACKUP_REMOVE" ->
336
        OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
337
      "OP_TEST_ALLOCATOR" ->
338
        OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
339
          genNameNE <*> genMaybe (pure []) <*> genMaybe (pure []) <*>
340
          arbitrary <*> genMaybe genNameNE <*>
341
          (genTags >>= mapM mkNonEmpty) <*>
342
          arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
343
          arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
344
          genMaybe genNamesNE <*> arbitrary <*> arbitrary
345
      "OP_TEST_JQUEUE" ->
346
        OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
347
          resize 20 (listOf genFQDN) <*> arbitrary
348
      "OP_TEST_DUMMY" ->
349
        OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
350
          pure J.JSNull <*> pure J.JSNull
351
      "OP_NETWORK_ADD" ->
352
        OpCodes.OpNetworkAdd <$> genNameNE <*> genIPv4Network <*>
353
          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
354
          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
355
          arbitrary <*> (genTags >>= mapM mkNonEmpty)
356
      "OP_NETWORK_REMOVE" ->
357
        OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
358
      "OP_NETWORK_SET_PARAMS" ->
359
        OpCodes.OpNetworkSetParams <$> genNameNE <*>
360
          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
361
          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
362
          genMaybe (listOf genIPv4Address)
363
      "OP_NETWORK_CONNECT" ->
364
        OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
365
          arbitrary <*> genNameNE <*> arbitrary
366
      "OP_NETWORK_DISCONNECT" ->
367
        OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
368
      "OP_NETWORK_QUERY" ->
369
        OpCodes.OpNetworkQuery <$> genFieldsNE <*> arbitrary <*> genNamesNE
370
      "OP_RESTRICTED_COMMAND" ->
371
        OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
372
          return Nothing <*> genNameNE
373
      _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
374

    
375
-- | Generates one element of a reason trail
376
genReasonElem :: Gen ReasonElem
377
genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
378

    
379
-- | Generates a reason trail
380
genReasonTrail :: Gen ReasonTrail
381
genReasonTrail = do
382
  size <- choose (0, 10)
383
  vectorOf size genReasonElem
384

    
385
instance Arbitrary OpCodes.CommonOpParams where
386
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
387
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
388
                genReasonTrail
389

    
390
-- * Helper functions
391

    
392
-- | Empty JSObject.
393
emptyJSObject :: J.JSObject J.JSValue
394
emptyJSObject = J.toJSObject []
395

    
396
-- | Empty maybe unchecked dictionary.
397
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
398
emptyMUD = genMaybe $ pure emptyJSObject
399

    
400
-- | Generates an empty container.
401
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
402
genEmptyContainer = pure . GenericContainer $ Map.fromList []
403

    
404
-- | Generates list of disk indices.
405
genDiskIndices :: Gen [DiskIndex]
406
genDiskIndices = do
407
  cnt <- choose (0, C.maxDisks)
408
  genUniquesList cnt arbitrary
409

    
410
-- | Generates a list of node names.
411
genNodeNames :: Gen [String]
412
genNodeNames = resize maxNodes (listOf genFQDN)
413

    
414
-- | Generates a list of node names in non-empty string type.
415
genNodeNamesNE :: Gen [NonEmptyString]
416
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
417

    
418
-- | Gets a node name in non-empty type.
419
genNodeNameNE :: Gen NonEmptyString
420
genNodeNameNE = genFQDN >>= mkNonEmpty
421

    
422
-- | Gets a name (non-fqdn) in non-empty type.
423
genNameNE :: Gen NonEmptyString
424
genNameNE = genName >>= mkNonEmpty
425

    
426
-- | Gets a list of names (non-fqdn) in non-empty type.
427
genNamesNE :: Gen [NonEmptyString]
428
genNamesNE = resize maxNodes (listOf genNameNE)
429

    
430
-- | Returns a list of non-empty fields.
431
genFieldsNE :: Gen [NonEmptyString]
432
genFieldsNE = genFields >>= mapM mkNonEmpty
433

    
434
-- | Generate a 3-byte MAC prefix.
435
genMacPrefix :: Gen NonEmptyString
436
genMacPrefix = do
437
  octets <- vectorOf 3 $ choose (0::Int, 255)
438
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
439

    
440
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
441
$(genArbitrary ''OpCodes.MetaOpCode)
442

    
443
-- | Small helper to check for a failed JSON deserialisation
444
isJsonError :: J.Result a -> Bool
445
isJsonError (J.Error _) = True
446
isJsonError _           = False
447

    
448
-- * Test cases
449

    
450
-- | Check that opcode serialization is idempotent.
451
prop_serialization :: OpCodes.OpCode -> Property
452
prop_serialization = testSerialisation
453

    
454
-- | Check that Python and Haskell defined the same opcode list.
455
case_AllDefined :: HUnit.Assertion
456
case_AllDefined = do
457
  py_stdout <-
458
     runPython "from ganeti import opcodes\n\
459
               \from ganeti import serializer\n\
460
               \import sys\n\
461
               \print serializer.Dump([opid for opid in opcodes.OP_MAPPING])\n"
462
               ""
463
     >>= checkPythonResult
464
  py_ops <- case J.decode py_stdout::J.Result [String] of
465
               J.Ok ops -> return ops
466
               J.Error msg ->
467
                 HUnit.assertFailure ("Unable to decode opcode names: " ++ msg)
468
                 -- this already raised an expection, but we need it
469
                 -- for proper types
470
                 >> fail "Unable to decode opcode names"
471
  let hs_ops = sort OpCodes.allOpIDs
472
      extra_py = py_ops \\ hs_ops
473
      extra_hs = hs_ops \\ py_ops
474
  HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
475
                    unlines extra_py) (null extra_py)
476
  HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
477
                    unlines extra_hs) (null extra_hs)
478

    
479
-- | Custom HUnit test case that forks a Python process and checks
480
-- correspondence between Haskell-generated OpCodes and their Python
481
-- decoded, validated and re-encoded version.
482
--
483
-- Note that we have a strange beast here: since launching Python is
484
-- expensive, we don't do this via a usual QuickProperty, since that's
485
-- slow (I've tested it, and it's indeed quite slow). Rather, we use a
486
-- single HUnit assertion, and in it we manually use QuickCheck to
487
-- generate 500 opcodes times the number of defined opcodes, which
488
-- then we pass in bulk to Python. The drawbacks to this method are
489
-- two fold: we cannot control the number of generated opcodes, since
490
-- HUnit assertions don't get access to the test options, and for the
491
-- same reason we can't run a repeatable seed. We should probably find
492
-- a better way to do this, for example by having a
493
-- separately-launched Python process (if not running the tests would
494
-- be skipped).
495
case_py_compat_types :: HUnit.Assertion
496
case_py_compat_types = do
497
  let num_opcodes = length OpCodes.allOpIDs * 100
498
  opcodes <- genSample (vectorOf num_opcodes
499
                                   (arbitrary::Gen OpCodes.MetaOpCode))
500
  let with_sum = map (\o -> (OpCodes.opSummary $
501
                             OpCodes.metaOpCode o, o)) opcodes
502
      serialized = J.encode opcodes
503
  -- check for non-ASCII fields, usually due to 'arbitrary :: String'
504
  mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
505
                HUnit.assertFailure $
506
                  "OpCode has non-ASCII fields: " ++ show op
507
        ) opcodes
508
  py_stdout <-
509
     runPython "from ganeti import opcodes\n\
510
               \from ganeti import serializer\n\
511
               \import sys\n\
512
               \op_data = serializer.Load(sys.stdin.read())\n\
513
               \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
514
               \for op in decoded:\n\
515
               \  op.Validate(True)\n\
516
               \encoded = [(op.Summary(), op.__getstate__())\n\
517
               \           for op in decoded]\n\
518
               \print serializer.Dump(encoded)" serialized
519
     >>= checkPythonResult
520
  let deserialised =
521
        J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
522
  decoded <- case deserialised of
523
               J.Ok ops -> return ops
524
               J.Error msg ->
525
                 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
526
                 -- this already raised an expection, but we need it
527
                 -- for proper types
528
                 >> fail "Unable to decode opcodes"
529
  HUnit.assertEqual "Mismatch in number of returned opcodes"
530
    (length decoded) (length with_sum)
531
  mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
532
        ) $ zip decoded with_sum
533

    
534
-- | Custom HUnit test case that forks a Python process and checks
535
-- correspondence between Haskell OpCodes fields and their Python
536
-- equivalent.
537
case_py_compat_fields :: HUnit.Assertion
538
case_py_compat_fields = do
539
  let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
540
                         OpCodes.allOpIDs
541
  py_stdout <-
542
     runPython "from ganeti import opcodes\n\
543
               \import sys\n\
544
               \from ganeti import serializer\n\
545
               \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
546
               \           for k, v in opcodes.OP_MAPPING.items()]\n\
547
               \print serializer.Dump(fields)" ""
548
     >>= checkPythonResult
549
  let deserialised = J.decode py_stdout::J.Result [(String, [String])]
550
  py_fields <- case deserialised of
551
                 J.Ok v -> return $ sort v
552
                 J.Error msg ->
553
                   HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
554
                   -- this already raised an expection, but we need it
555
                   -- for proper types
556
                   >> fail "Unable to decode op fields"
557
  HUnit.assertEqual "Mismatch in number of returned opcodes"
558
    (length hs_fields) (length py_fields)
559
  HUnit.assertEqual "Mismatch in defined OP_IDs"
560
    (map fst hs_fields) (map fst py_fields)
561
  mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
562
           HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
563
           HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
564
             py_flds hs_flds
565
        ) $ zip py_fields hs_fields
566

    
567
-- | Checks that setOpComment works correctly.
568
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
569
prop_setOpComment op comment =
570
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
571
  in OpCodes.opComment common ==? Just comment
572

    
573
-- | Tests wrong (negative) disk index.
574
prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
575
prop_mkDiskIndex_fail (Positive i) =
576
  case mkDiskIndex (negate i) of
577
    Bad msg -> printTestCase "error message " $
578
               "Invalid value" `isPrefixOf` msg
579
    Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
580
                       "' from negative value " ++ show (negate i)
581

    
582
-- | Tests a few invalid 'readRecreateDisks' cases.
583
case_readRecreateDisks_fail :: Assertion
584
case_readRecreateDisks_fail = do
585
  assertBool "null" $
586
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
587
  assertBool "string" $
588
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
589

    
590
-- | Tests a few invalid 'readDdmOldChanges' cases.
591
case_readDdmOldChanges_fail :: Assertion
592
case_readDdmOldChanges_fail = do
593
  assertBool "null" $
594
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
595
  assertBool "string" $
596
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
597

    
598
-- | Tests a few invalid 'readExportTarget' cases.
599
case_readExportTarget_fail :: Assertion
600
case_readExportTarget_fail = do
601
  assertBool "null" $
602
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
603
  assertBool "int" $
604
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
605

    
606
testSuite "OpCodes"
607
            [ 'prop_serialization
608
            , 'case_AllDefined
609
            , 'case_py_compat_types
610
            , 'case_py_compat_fields
611
            , 'prop_setOpComment
612
            , 'prop_mkDiskIndex_fail
613
            , 'case_readRecreateDisks_fail
614
            , 'case_readDdmOldChanges_fail
615
            , 'case_readExportTarget_fail
616
            ]