diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | CHANGELOG | 55 | ||||
-rw-r--r-- | doc/writing_tests.rst | 4 | ||||
-rw-r--r-- | functional_tests/support/issue720/test.py | 6 | ||||
-rw-r--r-- | functional_tests/test_failuredetail_plugin.py | 14 | ||||
-rw-r--r-- | nose/config.py | 3 | ||||
-rw-r--r-- | nose/failure.py | 3 | ||||
-rw-r--r-- | nose/plugins/capture.py | 27 | ||||
-rw-r--r-- | nose/plugins/failuredetail.py | 8 | ||||
-rw-r--r-- | nose/plugins/logcapture.py | 3 | ||||
-rw-r--r-- | nose/plugins/skip.py | 19 | ||||
-rw-r--r-- | nose/plugins/xunit.py | 55 | ||||
-rw-r--r-- | nose/pyversion.py | 65 | ||||
-rw-r--r-- | nose/suite.py | 1 | ||||
-rw-r--r-- | nose/usage.txt | 5 | ||||
-rw-r--r-- | nose/util.py | 9 | ||||
-rw-r--r-- | unit_tests/test_loader.py | 46 | ||||
-rw-r--r-- | unit_tests/test_logcapture_plugin.py | 20 | ||||
-rw-r--r-- | unit_tests/test_xunit.py | 34 |
19 files changed, 266 insertions, 112 deletions
@@ -24,3 +24,4 @@ Gary Donovan Brendan McCollam Erik Rose Sascha Peilicke +Andre Caron @@ -1,9 +1,64 @@ In Development +- The log capture plugin now correctly applies filters that were added + using `addFilter`. + Patch by Malthe Borch. - Corrected a reference to the multiprocessing plugin in the documentation. Patch by Nick Loadholtes. - Fixed #447: doctests fail when getpackage() returns None Patch by Matthew Brett. +- Fixed #749: xunit exceeds recursion limit + Patch by André Caron. +- Fixed a number of unicode-related issues. + Patch by John Szakmeister. +- Added the ability to ignore config files via an environment variable + Patch by Lukasz Balcerzak +- Fixed #720: nose with detailed errors raises encoding error + Patch by John Szakmeister. Thanks to Guillaume Ayoub for the test case. +- Fixed #692: UnicodeDecodeError in xunit when capturing stdout and stderr + Patch by John Szakmeister. +- Fixed #693: Python 2.4 incompatibilities + Patch by John Szakmeister. +- Don't save zero-byte xunit test reports + Patch by Dan Savilonis. +- Fix Importer.importFromPath to be able to import modules whose names start + with __init__ + Patch by Paul Bonser. +- Add a fake isatty() method to Tee + Patch by Jimmy Wennlund. +- Fix #700: Tee is missing the writelines() method + Patch by John Szakmeister. +- Fix #649: UnicodeDecodeError when an exception contains encoded strings + Patch by John Szakmeister. +- Fix #687: verbosity is not a flag + Patch by John Szakmeister. +- Fixed a suppressed deprecation warning + Patch by Arnon Yaari. +- Fixed some broken links in the documentation + Patch by Arnon Yaari. +- Add missing format parameter in error message + Patch by Etienne Millon. +- Switched docs to point at the GitHub site for the issue tracker + Patch by Daniel Beck. +- Fix #447: doctests fail when getpackage() returns None + Patch by Matthew Brett. +- Fix #366: make --pdb report on errors and failures. Use --pdb-error to get + the old behavior. + Patch by Arnon Yaari. +- Fix #501: Imported test generators are misrecognized as simple test + functions + Patch by John Szakmeister. +- Added a test for issue #501 + Patch by Michael Killough. +- Use SkipTest from unittest2, if available, for better integration with + testtools + Patch by Ian Wienand. +- Fix #759: Test failures with Python 3.4 + Patch by Barry Warsaw. +- Add a note about executable files in the usage, and how to workaround it + Patch by Michael Dunn. +- Fix #743: fix an incorrect regex in writing_tests.rst + Patch by Anne Moroney. 1.3.0 diff --git a/doc/writing_tests.rst b/doc/writing_tests.rst index d2418bd..4ccb9f4 100644 --- a/doc/writing_tests.rst +++ b/doc/writing_tests.rst @@ -3,7 +3,7 @@ Writing tests As with py.test_, nose tests need not be subclasses of :class:`unittest.TestCase`. Any function or class that matches the configured -testMatch regular expression (``(?:^|[\\b_\\.-])[Tt]est)`` by default -- that +testMatch regular expression (``(?:^|[\\b_\\.-])[Tt]est`` by default -- that is, has test or Test at a word boundary or following a - or _) and lives in a module that also matches that expression will be run as a test. For the sake of compatibility with legacy unittest test cases, nose will also load tests @@ -169,4 +169,4 @@ methods *do not* run before the generator method itself, as this would cause setUp to run twice before the first test without an intervening tearDown. Please note that method generators *are not* supported in `unittest.TestCase` -subclasses.
\ No newline at end of file +subclasses. diff --git a/functional_tests/support/issue720/test.py b/functional_tests/support/issue720/test.py new file mode 100644 index 0000000..0a194fd --- /dev/null +++ b/functional_tests/support/issue720/test.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +import unittest +class Test(unittest.TestCase): + def test(self): + print u"Unicöde" + assert 1 == 2 diff --git a/functional_tests/test_failuredetail_plugin.py b/functional_tests/test_failuredetail_plugin.py index 284cf49..8484461 100644 --- a/functional_tests/test_failuredetail_plugin.py +++ b/functional_tests/test_failuredetail_plugin.py @@ -46,5 +46,19 @@ class TestFailureDetailWithCapture(PluginTester, unittest.TestCase): assert expect in self.output +class TestFailureDetailWithUnicodeAndCapture(PluginTester, unittest.TestCase): + activate = "-d" + args = ['-v'] + plugins = [FailureDetail(), Capture()] + suitepath = os.path.join(support, 'issue720') + + def runTest(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + + assert 'UnicodeDecodeError' not in self.output + assert 'UnicodeEncodeError' not in self.output + if __name__ == '__main__': unittest.main() diff --git a/nose/config.py b/nose/config.py index 979fe3b..4214c2d 100644 --- a/nose/config.py +++ b/nose/config.py @@ -623,9 +623,6 @@ class NoOptions(object): def __getnewargs__(self): return () - def __getattr__(self, attr): - return None - def __nonzero__(self): return False diff --git a/nose/failure.py b/nose/failure.py index d24401c..c5fabfd 100644 --- a/nose/failure.py +++ b/nose/failure.py @@ -1,6 +1,7 @@ import logging import unittest from traceback import format_tb +from nose.pyversion import is_base_exception log = logging.getLogger(__name__) @@ -34,7 +35,7 @@ class Failure(unittest.TestCase): def runTest(self): if self.tb is not None: - if isinstance(self.exc_val, BaseException): + if is_base_exception(self.exc_val): raise self.exc_val, None, self.tb raise self.exc_class, self.exc_val, self.tb else: diff --git a/nose/plugins/capture.py b/nose/plugins/capture.py index 224f0a5..fa4e5dc 100644 --- a/nose/plugins/capture.py +++ b/nose/plugins/capture.py @@ -13,6 +13,7 @@ import logging import os import sys from nose.plugins.base import Plugin +from nose.pyversion import exc_to_unicode, force_unicode from nose.util import ln from StringIO import StringIO @@ -86,30 +87,8 @@ class Capture(Plugin): return self.formatError(test, err) def addCaptureToErr(self, ev, output): - if isinstance(ev, BaseException): - if hasattr(ev, '__unicode__'): - # 2.6+ - try: - ev = unicode(ev) - except UnicodeDecodeError: - # We need a unicode string... take our best shot at getting, - # since we don't know what the original encoding is in. - ev = str(ev).decode('utf8', 'replace') - else: - # 2.5- - if not hasattr(ev, 'message'): - # 2.4 - msg = len(ev.args) and ev.args[0] or '' - else: - msg = ev.message - if (isinstance(msg, basestring) and - not isinstance(msg, unicode)): - msg = msg.decode('utf8', 'replace') - ev = u'%s: %s' % (ev.__class__.__name__, msg) - elif not isinstance(ev, basestring): - ev = repr(ev) - if not isinstance(output, unicode): - output = output.decode('utf8', 'replace') + ev = exc_to_unicode(ev) + output = force_unicode(output) return u'\n'.join([ev, ln(u'>> begin captured stdout <<'), output, ln(u'>> end captured stdout <<')]) diff --git a/nose/plugins/failuredetail.py b/nose/plugins/failuredetail.py index 4c0729c..6462865 100644 --- a/nose/plugins/failuredetail.py +++ b/nose/plugins/failuredetail.py @@ -7,6 +7,7 @@ debugging information. """ from nose.plugins import Plugin +from nose.pyversion import exc_to_unicode, force_unicode from nose.inspector import inspect_traceback class FailureDetail(Plugin): @@ -38,10 +39,11 @@ class FailureDetail(Plugin): """Add detail from traceback inspection to error message of a failure. """ ec, ev, tb = err - tbinfo, str_ev = None, str(ev) + tbinfo, str_ev = None, exc_to_unicode(ev) + if tb: - tbinfo = inspect_traceback(tb) - str_ev = '\n'.join([str(ev), tbinfo]) + tbinfo = force_unicode(inspect_traceback(tb)) + str_ev = '\n'.join([str_ev, tbinfo]) test.tbinfo = tbinfo return (ec, str_ev, tb) diff --git a/nose/plugins/logcapture.py b/nose/plugins/logcapture.py index 30b4a96..101b335 100644 --- a/nose/plugins/logcapture.py +++ b/nose/plugins/logcapture.py @@ -85,7 +85,8 @@ class MyMemoryHandler(Handler): def truncate(self): self.buffer = [] def filter(self, record): - return self.filterset.allow(record.name) + if self.filterset.allow(record.name): + return Handler.filter(self, record) def __getstate__(self): state = self.__dict__.copy() del state['lock'] diff --git a/nose/plugins/skip.py b/nose/plugins/skip.py index 27e5162..9d1ac8f 100644 --- a/nose/plugins/skip.py +++ b/nose/plugins/skip.py @@ -9,15 +9,22 @@ is enabled by default but may be disabled with the ``--no-skip`` option. from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin +# on SkipTest: +# - unittest SkipTest is first preference, but it's only available +# for >= 2.7 +# - unittest2 SkipTest is second preference for older pythons. This +# mirrors logic for choosing SkipTest exception in testtools +# - if none of the above, provide custom class try: - # 2.7 from unittest.case import SkipTest except ImportError: - # 2.6 and below - class SkipTest(Exception): - """Raise this exception to mark a test as skipped. - """ - pass + try: + from unittest2.case import SkipTest + except ImportError: + class SkipTest(Exception): + """Raise this exception to mark a test as skipped. + """ + pass class Skip(ErrorClassPlugin): diff --git a/nose/plugins/xunit.py b/nose/plugins/xunit.py index 88255b9..d0aed88 100644 --- a/nose/plugins/xunit.py +++ b/nose/plugins/xunit.py @@ -49,7 +49,7 @@ from xml.sax import saxutils from nose.plugins.base import Plugin from nose.exc import SkipTest -from nose.pyversion import UNICODE_STRINGS +from nose.pyversion import force_unicode, format_exception # Invalid XML characters, control characters 0-31 sans \t, \n and \r CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]") @@ -112,26 +112,16 @@ def exc_message(exc_info): # Fallback to args as neither str nor # unicode(Exception(u'\xe6')) work in Python < 2.6 result = exc.args[0] + result = force_unicode(result, 'UTF-8') return xml_safe(result) -def format_exception(exc_info): - ec, ev, tb = exc_info - - # formatError() may have turned our exception object into a string, and - # Python 3's traceback.format_exception() doesn't take kindly to that (it - # expects an actual exception object). So we work around it, by doing the - # work ourselves if ev is a string. - if isinstance(ev, basestring): - tb_data = ''.join(traceback.format_tb(tb)) - return tb_data + ev - else: - return ''.join(traceback.format_exception(*exc_info)) - class Tee(object): - def __init__(self, *args): + def __init__(self, encoding, *args): + self._encoding = encoding self._streams = args def write(self, data): + data = force_unicode(data, self._encoding) for s in self._streams: s.write(data) @@ -173,8 +163,6 @@ class Xunit(Plugin): def _quoteattr(self, attr): """Escape an XML attribute. Value can be unicode.""" attr = xml_safe(attr) - if isinstance(attr, unicode) and not UNICODE_STRINGS: - attr = attr.encode(self.encoding) return saxutils.quoteattr(attr) def options(self, parser, env): @@ -217,7 +205,7 @@ class Xunit(Plugin): u'<testsuite name="nosetests" tests="%(total)d" ' u'errors="%(errors)d" failures="%(failures)d" ' u'skip="%(skipped)d">' % self.stats) - self.error_report_file.write(u''.join([self._forceUnicode(e) + self.error_report_file.write(u''.join([force_unicode(e, self.encoding) for e in self.errorlist])) self.error_report_file.write(u'</testsuite>') self.error_report_file.close() @@ -229,12 +217,15 @@ class Xunit(Plugin): self._capture_stack.append((sys.stdout, sys.stderr)) self._currentStdout = StringIO() self._currentStderr = StringIO() - sys.stdout = Tee(self._currentStdout, sys.stdout) - sys.stderr = Tee(self._currentStderr, sys.stderr) + sys.stdout = Tee(self.encoding, self._currentStdout, sys.stdout) + sys.stderr = Tee(self.encoding, self._currentStderr, sys.stderr) def startContext(self, context): self._startCapture() + def stopContext(self, context): + self._endCapture() + def beforeTest(self, test): """Initializes a timer before starting a test.""" self._timer = time() @@ -281,12 +272,13 @@ class Xunit(Plugin): type = 'error' self.stats['errors'] += 1 - tb = format_exception(err) + tb = format_exception(err, self.encoding) id = test.id() + self.errorlist.append( - '<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' - '<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' - '</%(type)s>%(systemout)s%(systemerr)s</testcase>' % + u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' + u'<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' + u'</%(type)s>%(systemout)s%(systemerr)s</testcase>' % {'cls': self._quoteattr(id_split(id)[0]), 'name': self._quoteattr(id_split(id)[-1]), 'taken': taken, @@ -302,13 +294,14 @@ class Xunit(Plugin): """Add failure output to Xunit report. """ taken = self._timeTaken() - tb = format_exception(err) + tb = format_exception(err, self.encoding) self.stats['failures'] += 1 id = test.id() + self.errorlist.append( - '<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' - '<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' - '</failure>%(systemout)s%(systemerr)s</testcase>' % + u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">' + u'<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>' + u'</failure>%(systemout)s%(systemerr)s</testcase>' % {'cls': self._quoteattr(id_split(id)[0]), 'name': self._quoteattr(id_split(id)[-1]), 'taken': taken, @@ -334,9 +327,3 @@ class Xunit(Plugin): 'systemout': self._getCapturedStdout(), 'systemerr': self._getCapturedStderr(), }) - - def _forceUnicode(self, s): - if not UNICODE_STRINGS: - if isinstance(s, str): - s = s.decode(self.encoding, 'replace') - return s diff --git a/nose/pyversion.py b/nose/pyversion.py index a6ec3f7..07c105f 100644 --- a/nose/pyversion.py +++ b/nose/pyversion.py @@ -3,19 +3,33 @@ This module contains fixups for using nose under different versions of Python. """ import sys import os +import traceback import types import inspect import nose.util __all__ = ['make_instancemethod', 'cmp_to_key', 'sort_list', 'ClassType', 'TypeType', 'UNICODE_STRINGS', 'unbound_method', 'ismethod', - 'bytes_'] + 'bytes_', 'is_base_exception', 'force_unicode', 'exc_to_unicode', + 'format_exception'] # In Python 3.x, all strings are unicode (the call to 'unicode()' in the 2.x # source will be replaced with 'str()' when running 2to3, so this test will # then become true) UNICODE_STRINGS = (type(unicode()) == type(str())) +if sys.version_info[:2] < (3, 0): + def force_unicode(s, encoding='UTF-8'): + try: + s = unicode(s) + except UnicodeDecodeError: + s = str(s).decode(encoding, 'replace') + + return s +else: + def force_unicode(s, encoding='UTF-8'): + return str(s) + # new.instancemethod() is obsolete for new-style classes (Python 3.x) # We need to use descriptor methods instead. try: @@ -147,3 +161,52 @@ else: return func.func_code.co_flags & CO_GENERATOR != 0 except AttributeError: return False + +# Make a function to help check if an exception is derived from BaseException. +# In Python 2.4, we just use Exception instead. +if sys.version_info[:2] < (2, 5): + def is_base_exception(exc): + return isinstance(exc, Exception) +else: + def is_base_exception(exc): + return isinstance(exc, BaseException) + +if sys.version_info[:2] < (3, 0): + def exc_to_unicode(ev, encoding='utf-8'): + if is_base_exception(ev): + if not hasattr(ev, '__unicode__'): + # 2.5- + if not hasattr(ev, 'message'): + # 2.4 + msg = len(ev.args) and ev.args[0] or '' + else: + msg = ev.message + msg = force_unicode(msg, encoding=encoding) + clsname = force_unicode(ev.__class__.__name__, + encoding=encoding) + ev = u'%s: %s' % (clsname, msg) + elif not isinstance(ev, unicode): + ev = repr(ev) + + return force_unicode(ev, encoding=encoding) +else: + def exc_to_unicode(ev, encoding='utf-8'): + return str(ev) + +def format_exception(exc_info, encoding='UTF-8'): + ec, ev, tb = exc_info + + # Our exception object may have been turned into a string, and Python 3's + # traceback.format_exception() doesn't take kindly to that (it expects an + # actual exception object). So we work around it, by doing the work + # ourselves if ev is not an exception object. + if not is_base_exception(ev): + tb_data = force_unicode( + ''.join(traceback.format_tb(tb)), + encoding) + ev = exc_to_unicode(ev) + return tb_data + ev + else: + return force_unicode( + ''.join(traceback.format_exception(*exc_info)), + encoding) diff --git a/nose/suite.py b/nose/suite.py index 3b68b23..18098ca 100644 --- a/nose/suite.py +++ b/nose/suite.py @@ -573,6 +573,7 @@ class FinalizingSuiteWrapper(unittest.TestSuite): control. """ def __init__(self, suite, finalize): + super(FinalizingSuiteWrapper, self).__init__() self.suite = suite self.finalize = finalize diff --git a/nose/usage.txt b/nose/usage.txt index 21ef5ce..bc96894 100644 --- a/nose/usage.txt +++ b/nose/usage.txt @@ -17,6 +17,11 @@ may use the assert keyword or raise AssertionErrors to indicate test failure. TestCase subclasses may do the same or use the various TestCase methods available. +**It is important to note that the default behavior of nose is to +not include tests from files which are executable.** To include +tests from such files, remove their executable bit or use +the --exe flag (see 'Options' section below). + Selecting Tests --------------- diff --git a/nose/util.py b/nose/util.py index 039163a..7995700 100644 --- a/nose/util.py +++ b/nose/util.py @@ -609,8 +609,13 @@ def transplant_func(func, module): """ from nose.tools import make_decorator - def newfunc(*arg, **kw): - return func(*arg, **kw) + if isgenerator(func): + def newfunc(*arg, **kw): + for v in func(*arg, **kw): + yield v + else: + def newfunc(*arg, **kw): + return func(*arg, **kw) newfunc = make_decorator(func)(newfunc) newfunc.__module__ = module diff --git a/unit_tests/test_loader.py b/unit_tests/test_loader.py index 1f622a5..e2dfcc4 100644 --- a/unit_tests/test_loader.py +++ b/unit_tests/test_loader.py @@ -35,6 +35,8 @@ def mods(): M['test_module_with_metaclass_tests'] = imp.new_module( 'test_module_with_metaclass_tests') M['test_transplant'] = imp.new_module('test_transplant') + M['test_module_transplant_generator'] = imp.new_module( + 'test_module_transplant_generator') # a unittest testcase subclass class TC(unittest.TestCase): @@ -112,6 +114,14 @@ def mods(): def runTest(self): pass + def test_func_generator_transplant(): + """docstring for transplanted test func generator + """ + def test_odd(v): + assert v % 2 + for i in range(0, 4): + yield test_odd, i + M['nose'] = nose M['__main__'] = sys.modules['__main__'] M['test_module'].TC = TC @@ -133,6 +143,9 @@ def mods(): TestMetaclassed.__module__ = 'test_module_with_metaclass_tests' M['test_transplant'].Transplant = Transplant Transplant.__module__ = 'test_class_source' + M['test_module_transplant_generator'].test_func_generator_transplant = \ + test_func_generator_transplant + # don't set test_func_generator_transplant.__module__ so it is transplanted del TC del TC2 del TestMetaclassed @@ -141,6 +154,7 @@ def mods(): del TestClass del test_func_generator del Transplant + del test_func_generator_transplant return M M = mods() @@ -514,7 +528,37 @@ class TestTestLoader(unittest.TestCase): count += 1 assert count == 4, \ "Expected to generate 4 tests, but got %s" % count - + + def test_load_transplanted_generator(self): + print "load transplanted generator (issue 501)" + test_module_transplant_generator = M['test_module_transplant_generator'] + l = self.l + suite = l.loadTestsFromModule(test_module_transplant_generator) + tests = [t for t in suite] + + assert len(tests) == 1 + print "test", tests[0] + assert isinstance(tests[0], unittest.TestSuite), \ + "Test is not a suite - probably did not look like a generator" + + count = 0 + for t in tests[0]: + print "generated test %s" % t + print t.shortDescription() + assert isinstance(t, nose.case.Test), \ + "Test %s is not a Test?" % t + assert isinstance(t.test, nose.case.FunctionTestCase), \ + "Test %s is not a FunctionTestCase" % t.test + assert 'test_func_generator' in str(t), \ + "Bad str val '%s' for test" % str(t) + assert 'docstring for transplanted test func generator' \ + in t.shortDescription(), \ + "Bad shortDescription '%s' for test %s" % \ + (t.shortDescription(), t) + count += 1 + assert count == 4, \ + "Expected to generate 4 tests, but got %s" % count + if __name__ == '__main__': #import logging #logging.basicConfig(level=logging.DEBUG) diff --git a/unit_tests/test_logcapture_plugin.py b/unit_tests/test_logcapture_plugin.py index 63d1f8b..63aa651 100644 --- a/unit_tests/test_logcapture_plugin.py +++ b/unit_tests/test_logcapture_plugin.py @@ -155,6 +155,26 @@ class TestLogCapturePlugin(object): eq_(1, len(records)) eq_("++Hello++", records[0]) + def test_builtin_logging_filtering(self): + c = LogCapture() + c.logformat = '++%(message)s++' + c.start() + log = logging.getLogger("foobar.something") + filtered = [] + class filter(object): + def filter(record): + filtered.append(record) + return len(filtered) == 1 + filter = staticmethod(filter) + c.handler.addFilter(filter) + log.debug("Hello") + log.debug("World") + c.end() + eq_(2, len(filtered)) + records = c.formatLogRecords() + eq_(1, len(records)) + eq_("++Hello++", records[0]) + def test_logging_filter(self): env = {'NOSE_LOGFILTER': 'foo,bar'} c = LogCapture() diff --git a/unit_tests/test_xunit.py b/unit_tests/test_xunit.py index c141739..d98ccba 100644 --- a/unit_tests/test_xunit.py +++ b/unit_tests/test_xunit.py @@ -23,40 +23,6 @@ mktest.__test__ = False time_taken = re.compile(r'\d\.\d\d') -class TestEscaping(unittest.TestCase): - - def setUp(self): - self.x = Xunit() - - def test_all(self): - eq_(self.x._quoteattr( - '''<baz src="http://foo?f=1&b=2" quote="inix hubris 'maximus'?" />'''), - ('"<baz src="http://foo?f=1&b=2" ' - 'quote="inix hubris \'maximus\'?" />"')) - - def test_unicode_is_utf8_by_default(self): - if not UNICODE_STRINGS: - eq_(self.x._quoteattr(u'Ivan Krsti\u0107'), - '"Ivan Krsti\xc4\x87"') - - def test_unicode_custom_utf16_madness(self): - self.x.encoding = 'utf-16' - utf16 = self.x._quoteattr(u'Ivan Krsti\u0107')[1:-1] - - if UNICODE_STRINGS: - # If all internal strings are unicode, then _quoteattr shouldn't - # have changed anything. - eq_(utf16, u'Ivan Krsti\u0107') - else: - # to avoid big/little endian bytes, assert that we can put it back: - eq_(utf16.decode('utf16'), u'Ivan Krsti\u0107') - - def test_control_characters(self): - # quoting of \n, \r varies in diff. python versions - n = saxutils.quoteattr('\n')[1:-1] - r = saxutils.quoteattr('\r')[1:-1] - eq_(self.x._quoteattr('foo\n\b\f\r'), '"foo%s??%s"' % (n, r)) - eq_(escape_cdata('foo\n\b\f\r'), 'foo\n??\r') class TestSplitId(unittest.TestCase): |