From 0384c4576a642fd10b403fe3bb1af0ce288f7158 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Mon, 20 Aug 2012 19:22:24 +0200 Subject: [PATCH] Add test for checking Haskell/Python opcode equivalence This is a very big hack for testing the equivalence of Python and Haskell opcode definitions. See the docstring for details; I'm not very happy with the solution but it does the job. An alternate option would be to launch the Python code when initialising the tests, pass (somehow) the resource through all of the test suite to this function, and then use normal QuickCheck properties. Or maybe we find a better solution later; for now, this does the job. Signed-off-by: Iustin Pop <iustin@google.com> Reviewed-by: Agata Murawska <agatamurawska@google.com> --- htools/Ganeti/HTools/QC.hs | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index a440e15d5..37fded7d8 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 -- GitLab