diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index a440e15d5575bf3dca4a4c65606d667fb65db484..37fded7d85b07145d0d186e232f34dfbe858c7cf 100644 --- a/htools/Ganeti/HTools/QC.hs +++ b/htools/Ganeti/HTools/QC.hs @@ -1636,9 +1636,57 @@ case_OpCodes_AllDefined = do HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++ unlines extra_hs) (null extra_hs) +-- | Custom HUnit test case that forks a Python process and checks +-- correspondence between Haskell-generated OpCodes and their Python +-- decoded, validated and re-encoded version. +-- +-- Note that we have a strange beast here: since launching Python is +-- expensive, we don't do this via a usual QuickProperty, since that's +-- slow (I've tested it, and it's indeed quite slow). Rather, we use a +-- single HUnit assertion, and in it we manually use QuickCheck to +-- generate 500 opcodes times the number of defined opcodes, which +-- then we pass in bulk to Python. The drawbacks to this method are +-- two fold: we cannot control the number of generated opcodes, since +-- HUnit assertions don't get access to the test options, and for the +-- same reason we can't run a repeatable seed. We should probably find +-- a better way to do this, for example by having a +-- separately-launched Python process (if not running the tests would +-- be skipped). +case_OpCodes_py_compat :: HUnit.Assertion +case_OpCodes_py_compat = do + let num_opcodes = length OpCodes.allOpIDs * 500 + sample_opcodes <- sample' (vectorOf num_opcodes + (arbitrary::Gen OpCodes.OpCode)) + let opcodes = head sample_opcodes + serialized = J.encode opcodes + py_stdout <- + runPython "from ganeti import opcodes\n\ + \import sys\n\ + \from ganeti import serializer\n\ + \op_data = serializer.Load(sys.stdin.read())\n\ + \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\ + \for op in decoded:\n\ + \ op.Validate(True)\n\ + \encoded = [op.__getstate__() for op in decoded]\n\ + \print serializer.Dump(encoded)" serialized + >>= checkPythonResult + let deserialised = (J.decode py_stdout::J.Result [OpCodes.OpCode]) + decoded <- case deserialised of + J.Ok ops -> return ops + J.Error msg -> + HUnit.assertFailure ("Unable to decode opcodes: " ++ msg) + -- this already raised an expection, but we need it + -- for proper types + >> fail "Unable to decode opcodes" + HUnit.assertEqual "Mismatch in number of returned opcodes" + (length opcodes) (length decoded) + mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding") + ) $ zip opcodes decoded + testSuite "OpCodes" [ 'prop_OpCodes_serialization , 'case_OpCodes_AllDefined + , 'case_OpCodes_py_compat ] -- ** Jobs tests