From 8bc17ebb9a35d93fe4ea052aa4c09790fad1be08 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Mon, 10 Dec 2012 09:27:07 +0100
Subject: [PATCH] Add optional formatting for OP_DSC_FIELD

For some opcodes, the output is not "stable", and depends on the exact
input values; this makes it harder to check consistency against
Haskell code.

To compensate for this, we add a way to override the formatting of the
OP_DSC_FIELD; by default, this is always "%s", but if the
OP_DSC_FORMATTER is defined (must be a callable), it is used to format
the actual value.

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 lib/opcodes.py                  | 22 +++++++++++++++++++++-
 test/ganeti.opcodes_unittest.py | 10 ++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/lib/opcodes.py b/lib/opcodes.py
index fd6163953..6e3fed502 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -434,6 +434,10 @@ class _AutoOpParamSlots(objectutils.AutoSlots):
     slots = mcs._GetSlots(attrs)
     assert "OP_DSC_FIELD" not in attrs or attrs["OP_DSC_FIELD"] in slots, \
       "Class '%s' uses unknown field in OP_DSC_FIELD" % name
+    assert ("OP_DSC_FORMATTER" not in attrs or
+            callable(attrs["OP_DSC_FORMATTER"])), \
+      ("Class '%s' uses non-callable in OP_DSC_FORMATTER (%s)" %
+       (name, type(attrs["OP_DSC_FORMATTER"])))
 
     attrs["OP_ID"] = _NameToId(name)
 
@@ -598,6 +602,9 @@ class OpCode(BaseOpCode):
   @cvar OP_DSC_FIELD: The name of a field whose value will be included in the
                       string returned by Summary(); see the docstring of that
                       method for details).
+  @cvar OP_DSC_FORMATTER: A callable that should format the OP_DSC_FIELD; if
+                          not present, then the field will be simply converted
+                          to string
   @cvar OP_PARAMS: List of opcode attributes, the default values they should
                    get if not already defined, and types they must match.
   @cvar OP_RESULT: Callable to verify opcode result
@@ -685,7 +692,10 @@ class OpCode(BaseOpCode):
     field_name = getattr(self, "OP_DSC_FIELD", None)
     if field_name:
       field_value = getattr(self, field_name, None)
-      if isinstance(field_value, (list, tuple)):
+      field_formatter = getattr(self, "OP_DSC_FORMATTER", None)
+      if callable(field_formatter):
+        field_value = field_formatter(field_value)
+      elif isinstance(field_value, (list, tuple)):
         field_value = ",".join(str(i) for i in field_value)
       txt = "%s(%s)" % (txt, field_value)
     return txt
@@ -1958,6 +1968,16 @@ class OpTestDelay(OpCode):
     ("repeat", 0, ht.TNonNegativeInt, None),
     ]
 
+  def OP_DSC_FORMATTER(self, value): # pylint: disable=C0103,R0201
+    """Custom formatter for duration.
+
+    """
+    try:
+      v = float(value)
+    except TypeError:
+      v = value
+    return str(v)
+
 
 class OpTestAllocator(OpCode):
   """Allocator framework testing.
diff --git a/test/ganeti.opcodes_unittest.py b/test/ganeti.opcodes_unittest.py
index 3e2db17a5..9cc80ffea 100755
--- a/test/ganeti.opcodes_unittest.py
+++ b/test/ganeti.opcodes_unittest.py
@@ -121,6 +121,16 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(OpTest(data="node1.example.com").Summary(),
                      "TEST(node1.example.com)")
 
+  def testSummaryFormatter(self):
+    class OpTest(opcodes.OpCode):
+      OP_DSC_FIELD = "data"
+      OP_DSC_FORMATTER = lambda _, v: "a"
+      OP_PARAMS = [
+        ("data", ht.NoDefault, ht.TString, None),
+        ]
+    self.assertEqual(OpTest(data="").Summary(), "TEST(a)")
+    self.assertEqual(OpTest(data="b").Summary(), "TEST(a)")
+
   def testTinySummary(self):
     self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
     self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
-- 
GitLab