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