From 4d4bc6f384a920daf1710101ac2a5dc68c49fc01 Mon Sep 17 00:00:00 2001 From: Brant Knudson Date: Wed, 9 Dec 2015 13:12:01 -0600 Subject: [PATCH] Add unique_text_generator This provides a way to make it easier to test with unicode text strings. The value returned will be like TestCase.getUniqueString but the value is six.text_type and contains unicode. Change-Id: I3f144e1294a801b23793f7a2520465e15f3a5222 --- NEWS | 4 +++ doc/for-test-authors.rst | 4 +++ requirements.txt | 1 + testtools/__init__.py | 2 ++ testtools/testcase.py | 40 ++++++++++++++++++++++++++ testtools/tests/test_testcase.py | 49 +++++++++++++++++++++++++++++++- 6 files changed, 99 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index a2f8a525..013b83b8 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,10 @@ Improvements report the lack of result as a test error. This ought to make weird concurrency interaction bugs easier to understand. (Jonathan Lange) +* Introduce the unique_text_generator generator function. Similar to the + getUniqueString() method, except it creates unique unicode text strings. + (Brant Knudson) + * Previously, when gathering details caused by a setUp() failure, a traceback occurred if the fixture used the newer _setUp(). This had the side effect of not clearing up fixtures nor gathering details diff --git a/doc/for-test-authors.rst b/doc/for-test-authors.rst index 06a107d7..eec27831 100644 --- a/doc/for-test-authors.rst +++ b/doc/for-test-authors.rst @@ -1357,6 +1357,10 @@ details of certain variables don't actually matter. See pages 419-423 of `xUnit Test Patterns`_ by Gerard Meszaros for a detailed discussion of creation methods. +``testcase.generate_unique_text()`` is similar to ``getUniqueString``, except +it generates text that contains unicode characters. The value will be a +``unicode`` object in Python 2 and a ``str`` object in Python 3. + Test attributes --------------- diff --git a/requirements.txt b/requirements.txt index 100d8e42..73152ec0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ pyrsistent python-mimeparse unittest2>=1.0.0 traceback2 +six>=1.4.0 diff --git a/testtools/__init__.py b/testtools/__init__.py index 336df243..d6c15d2a 100644 --- a/testtools/__init__.py +++ b/testtools/__init__.py @@ -42,6 +42,7 @@ 'TimestampingStreamResult', 'try_import', 'try_imports', + 'unique_text_generator', ] # Compat - removal announced in 0.9.25. @@ -77,6 +78,7 @@ skip, skipIf, skipUnless, + unique_text_generator, ) from testtools.testresult import ( CopyStreamResult, diff --git a/testtools/testcase.py b/testtools/testcase.py index 97160e04..1e131eeb 100644 --- a/testtools/testcase.py +++ b/testtools/testcase.py @@ -13,6 +13,7 @@ 'skipIf', 'skipUnless', 'TestCase', + 'unique_text_generator', ] import copy @@ -29,6 +30,7 @@ fixtures = try_import('fixtures') # To let setup.py work, make this a conditional import. unittest = try_imports(['unittest2', 'unittest']) +import six from testtools import ( content, @@ -182,6 +184,44 @@ def gather_details(source_dict, target_dict): target_dict[name] = _copy_content(content_object) +def _mods(i, mod): + (q, r) = divmod(i, mod) + while True: + yield r + if not q: + break + (q, r) = divmod(q, mod) + + +def _unique_text(base_cp, cp_range, index): + s = six.text_type('') + for m in _mods(index, cp_range): + s += six.unichr(base_cp + m) + return s + + +def unique_text_generator(prefix): + """Generates unique text values. + + Generates text values that are unique. Use this when you need arbitrary + text in your test, or as a helper for custom anonymous factory methods. + + :param prefix: The prefix for text. + :return: text that looks like '-'. + :rtype: six.text_type + """ + # 0x1e00 is the start of a range of glyphs that are easy to see are + # unicode since they've got circles and dots and other diacriticals. + # 0x1eff is the end of the range of these diacritical chars. + BASE_CP = 0x1e00 + CP_RANGE = 0x1f00 - BASE_CP + index = 0 + while True: + unique_text = _unique_text(BASE_CP, CP_RANGE, index) + yield six.text_type('%s-%s') % (prefix, unique_text) + index = index + 1 + + class TestCase(unittest.TestCase): """Extensions to the basic TestCase. diff --git a/testtools/tests/test_testcase.py b/testtools/tests/test_testcase.py index 91536800..f0aa881d 100644 --- a/testtools/tests/test_testcase.py +++ b/testtools/tests/test_testcase.py @@ -7,6 +7,8 @@ import sys import unittest +import six + from testtools import ( DecorateTestCaseResult, ErrorHolder, @@ -1117,7 +1119,7 @@ def test_passes_unexpectedly(self): class TestUniqueFactories(TestCase): - """Tests for getUniqueString and getUniqueInteger.""" + """Tests for getUniqueString, getUniqueInteger, unique_text_generator.""" run_test_with = FullStackRunTest @@ -1145,6 +1147,51 @@ def test_getUniqueString_prefix(self): name_two = self.getUniqueString('bar') self.assertThat(name_two, Equals('bar-2')) + def test_unique_text_generator(self): + # unique_text_generator yields the prefix's id followed by unique + # unicode string. + prefix = self.getUniqueString() + unique_text_generator = testcase.unique_text_generator(prefix) + first_result = next(unique_text_generator) + self.assertEqual(six.text_type('%s-%s') % (prefix, _u('\u1e00')), + first_result) + # The next value yielded by unique_text_generator is different. + second_result = next(unique_text_generator) + self.assertEqual(six.text_type('%s-%s') % (prefix, _u('\u1e01')), + second_result) + + def test_mods(self): + # given a number and max, generate a list that's the mods. + # The list should contain no numbers >= mod + self.assertEqual([0], list(testcase._mods(0, 5))) + self.assertEqual([1], list(testcase._mods(1, 5))) + self.assertEqual([2], list(testcase._mods(2, 5))) + self.assertEqual([3], list(testcase._mods(3, 5))) + self.assertEqual([4], list(testcase._mods(4, 5))) + self.assertEqual([0, 1], list(testcase._mods(5, 5))) + self.assertEqual([1, 1], list(testcase._mods(6, 5))) + self.assertEqual([2, 1], list(testcase._mods(7, 5))) + self.assertEqual([0, 2], list(testcase._mods(10, 5))) + self.assertEqual([0, 0, 1], list(testcase._mods(25, 5))) + self.assertEqual([1, 0, 1], list(testcase._mods(26, 5))) + self.assertEqual([1], list(testcase._mods(1, 100))) + self.assertEqual([0, 1], list(testcase._mods(100, 100))) + self.assertEqual([0, 10], list(testcase._mods(1000, 100))) + + def test_unique_text(self): + self.assertEqual( + u'\u1e00', + testcase._unique_text(base_cp=0x1e00, cp_range=5, index=0)) + self.assertEqual( + u'\u1e01', + testcase._unique_text(base_cp=0x1e00, cp_range=5, index=1)) + self.assertEqual( + u'\u1e00\u1e01', + testcase._unique_text(base_cp=0x1e00, cp_range=5, index=5)) + self.assertEqual( + u'\u1e03\u1e02\u1e01', + testcase._unique_text(base_cp=0x1e00, cp_range=5, index=38)) + class TestCloneTestWithNewId(TestCase): """Tests for clone_test_with_new_id."""