diff --git a/lib/mcpu.py b/lib/mcpu.py index 96c589b7f43dcaa4b4fa99fe952c96669d0ede3d..8b3734e0ebf2e6e6c8c459c8041da7c2c5e17813 100644 --- a/lib/mcpu.py +++ b/lib/mcpu.py @@ -404,8 +404,8 @@ class Processor(object): if not (resultcheck_fn is None or resultcheck_fn(result)): logging.error("Expected opcode result matching %s, got %s", resultcheck_fn, result) - raise errors.OpResultError("Opcode result does not match %s, got %s" % - (resultcheck_fn, result[:80])) + raise errors.OpResultError("Opcode result does not match %s: %s" % + (resultcheck_fn, utils.Truncate(result, 80))) return result diff --git a/lib/utils/text.py b/lib/utils/text.py index 02382642bff0282a6d831dc09d2e2e4fbadbaefb..0a0e68cebe1d18d3e1c146f7cca8cacad2e34d2f 100644 --- a/lib/utils/text.py +++ b/lib/utils/text.py @@ -43,6 +43,9 @@ _MAC_CHECK_RE = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I) #: Shell param checker regexp _SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$") +#: ASCII equivalent of unicode character 'HORIZONTAL ELLIPSIS' (U+2026) +_ASCII_ELLIPSIS = "..." + def MatchNameComponent(key, name_list, case_sensitive=True): """Try to match a name against a list. @@ -556,3 +559,26 @@ def FormatOrdinal(value): suffix = "th" return "%s%s" % (value, suffix) + + +def Truncate(text, length): + """Truncate string and add ellipsis if needed. + + @type text: string + @param text: Text + @type length: integer + @param length: Desired length + @rtype: string + @return: Truncated text + + """ + assert length > len(_ASCII_ELLIPSIS) + + # Serialize if necessary + if not isinstance(text, basestring): + text = str(text) + + if len(text) <= length: + return text + else: + return text[:length - len(_ASCII_ELLIPSIS)] + _ASCII_ELLIPSIS diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py index 1e91468e3195670979e02b65907e0ff6b8c998dd..5a9af02e7eba0459797cc2f385c1a77e4a240e5d 100755 --- a/test/ganeti.utils.text_unittest.py +++ b/test/ganeti.utils.text_unittest.py @@ -545,5 +545,32 @@ class TestOrdinal(unittest.TestCase): self.assertEqual(utils.FormatOrdinal(value), ordinal) +class TestTruncate(unittest.TestCase): + def _Test(self, text, length): + result = utils.Truncate(text, length) + self.assertTrue(len(result) <= length) + return result + + def test(self): + self.assertEqual(self._Test("", 80), "") + self.assertEqual(self._Test("abc", 4), "abc") + self.assertEqual(self._Test("Hello World", 80), "Hello World") + self.assertEqual(self._Test("Hello World", 4), "H...") + self.assertEqual(self._Test("Hello World", 5), "He...") + + for i in [4, 10, 100]: + data = i * "FooBarBaz" + self.assertEqual(self._Test(data, len(data)), data) + + for (length, exp) in [(8, u"T\u00e4st\u2026xyz"), (7, u"T\u00e4st...")]: + self.assertEqual(self._Test(u"T\u00e4st\u2026xyz", length), exp) + + self.assertEqual(self._Test(range(100), 20), "[0, 1, 2, 3, 4, 5...") + + def testError(self): + for i in range(4): + self.assertRaises(AssertionError, utils.Truncate, "", i) + + if __name__ == "__main__": testutils.GanetiTestProgram()