diff --git a/lib/serializer.py b/lib/serializer.py index b54cd92f29265f862ce6a9c396aa7104b865a614..2b78efd6003e46a3704ddb82c426a68155cc1c6a 100644 --- a/lib/serializer.py +++ b/lib/serializer.py @@ -36,16 +36,41 @@ try: except ImportError: import sha as sha1 -# Check whether the simplejson module supports indentation + _JSON_INDENT = 2 -try: - simplejson.dumps(1, indent=_JSON_INDENT) -except TypeError: - _JSON_INDENT = None _RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE) +def _GetJsonDumpers(): + """Returns two JSON functions to serialize data. + + @rtype: (callable, callable) + @return: The function to generate a compact form of JSON and another one to + generate a more readable, indented form of JSON (if supported) + + """ + plain_dump = simplejson.dumps + + # Check whether the simplejson module supports indentation + try: + simplejson.dumps(1, indent=_JSON_INDENT) + except TypeError: + # Indentation not supported + indent_dump = plain_dump + else: + # Indentation supported + indent_dump = lambda data: simplejson.dumps(data, indent=_JSON_INDENT) + + assert callable(plain_dump) + assert callable(indent_dump) + + return (plain_dump, indent_dump) + + +(_DumpJson, _DumpJsonIndent) = _GetJsonDumpers() + + def DumpJson(data, indent=True): """Serialize a given object. @@ -55,14 +80,15 @@ def DumpJson(data, indent=True): @return: the string representation of data """ - if not indent or _JSON_INDENT is None: - txt = simplejson.dumps(data) + if indent: + fn = _DumpJsonIndent else: - txt = simplejson.dumps(data, indent=_JSON_INDENT, sort_keys=True) + fn = _DumpJson - txt = _RE_EOLSP.sub("", txt) + txt = _RE_EOLSP.sub("", fn(data)) if not txt.endswith('\n'): txt += '\n' + return txt diff --git a/test/ganeti.serializer_unittest.py b/test/ganeti.serializer_unittest.py index 8dd8aea3b5693187a25eb2e2ef8f8bd148b50580..9b8a97e6d712e389bd434eccd302a36e16c7e1f8 100755 --- a/test/ganeti.serializer_unittest.py +++ b/test/ganeti.serializer_unittest.py @@ -27,16 +27,10 @@ import unittest from ganeti import serializer from ganeti import errors +import testutils -class SimplejsonMock(object): - def dumps(self, data, indent=None): - return repr(data) - def loads(self, data): - return eval(data) - - -class TestSerializer(unittest.TestCase): +class TestSerializer(testutils.GanetiTestCase): """Serializer tests""" _TESTDATA = [ @@ -44,15 +38,23 @@ class TestSerializer(unittest.TestCase): 255, [1, 2, 3], (1, 2, 3), - { 1: 2, "foo": "bar", }, + { "1": 2, "foo": "bar", }, + ["abc", 1, 2, 3, 999, + { + "a1": ("Hello", "World"), + "a2": "This is only a test", + "a3": None, + }, + { + "foo": "bar", + }, + ] ] - def setUp(self): - self._orig_simplejson = serializer.simplejson - serializer.simplejson = SimplejsonMock() - - def tearDown(self): - serializer.simplejson = self._orig_simplejson + def _TestSerializer(self, dump_fn, load_fn): + for data in self._TESTDATA: + self.failUnless(dump_fn(data).endswith("\n")) + self.assertEqualValues(load_fn(dump_fn(data)), data) def testGeneric(self): return self._TestSerializer(serializer.Dump, serializer.Load) @@ -65,20 +67,17 @@ class TestSerializer(unittest.TestCase): DumpSigned = serializer.DumpSigned for data in self._TESTDATA: - self.assertEqual(LoadSigned(DumpSigned(data, "mykey"), "mykey"), - (data, '')) - self.assertEqual(LoadSigned( - DumpSigned(data, "myprivatekey", "mysalt"), - "myprivatekey"), (data, "mysalt")) + self.assertEqualValues(LoadSigned(DumpSigned(data, "mykey"), "mykey"), + (data, '')) + self.assertEqualValues(LoadSigned(DumpSigned(data, "myprivatekey", + "mysalt"), + "myprivatekey"), + (data, "mysalt")) + self.assertRaises(errors.SignatureError, serializer.LoadSigned, serializer.DumpSigned("test", "myprivatekey"), "myotherkey") - def _TestSerializer(self, dump_fn, load_fn): - for data in self._TESTDATA: - self.failUnless(dump_fn(data).endswith("\n")) - self.failUnlessEqual(load_fn(dump_fn(data)), data) - if __name__ == '__main__': unittest.main() diff --git a/test/testutils.py b/test/testutils.py index b719ce2398ca6df9a973e19db0c3176b84f63e16..5e36a175460dcdfb3cce5448c7b9f2c7590d099b 100644 --- a/test/testutils.py +++ b/test/testutils.py @@ -75,6 +75,16 @@ class GanetiTestCase(unittest.TestCase): actual_mode = stat.S_IMODE(st.st_mode) self.assertEqual(actual_mode, expected_mode) + def assertEqualValues(self, first, second, msg=None): + """Compares two values whether they're equal. + + Tuples are automatically converted to lists before comparing. + + """ + return self.assertEqual(UnifyValueType(first), + UnifyValueType(second), + msg=msg) + @staticmethod def _TestDataFilename(name): """Returns the filename of a given test data file. @@ -97,7 +107,6 @@ class GanetiTestCase(unittest.TestCase): proper test file name. """ - return utils.ReadFile(cls._TestDataFilename(name)) def _CreateTempFile(self): @@ -111,3 +120,19 @@ class GanetiTestCase(unittest.TestCase): os.close(fh) self._temp_files.append(fname) return fname + + +def UnifyValueType(data): + """Converts all tuples into lists. + + This is useful for unittests where an external library doesn't keep types. + + """ + if isinstance(data, (tuple, list)): + return [UnifyValueType(i) for i in data] + + elif isinstance(data, dict): + return dict([(UnifyValueType(key), UnifyValueType(value)) + for (key, value) in data.iteritems()]) + + return data