From 67c529190c57b5ff47de51694a21a173223da0de Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 14 Aug 2017 11:53:34 -0500 Subject: 100% test coverage Fixes #9 Two potentially important changes: - Removed unused code in the tests/ package. If other packages were using it, they will need updates. (They shouldn't be, but that's happened before.) - Changed TraversableModuleImporter to catch ImportError instead of KeyError. There's no way the underlying SimpleModuleImporter can raise a KeyError anymore. I think this is the desired semantic but I am not 100% sure. --- CHANGES.rst | 9 + src/zope/pagetemplate/engine.py | 25 +-- src/zope/pagetemplate/pagetemplate.py | 22 +-- src/zope/pagetemplate/pagetemplatefile.py | 26 ++- src/zope/pagetemplate/tests/batch.py | 107 ++++-------- src/zope/pagetemplate/tests/test_basictemplate.py | 194 ++++++++++++++-------- src/zope/pagetemplate/tests/test_engine.py | 140 +++++++++++++--- src/zope/pagetemplate/tests/test_htmltests.py | 25 ++- src/zope/pagetemplate/tests/test_ptfile.py | 98 +++++++---- src/zope/pagetemplate/tests/util.py | 71 +++----- tox.ini | 2 +- 11 files changed, 419 insertions(+), 300 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 710108f..4d529ba 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,15 @@ - Drop support for Python 2.6, 3.2 and 3.3. +- Certain internal test support objects in the ``tests`` package were + removed or modified. + +- The ``TraversableModuleImporter`` properly turns ``ImportError`` + into ``TraversalError``. Previously it was catching ``KeyError``, + which cannot be raised. + +- Reach 100% code coverage and maintain it through automated testing. + 4.2.1 (2015-06-06) ================== diff --git a/src/zope/pagetemplate/engine.py b/src/zope/pagetemplate/engine.py index f3687d7..cea32d7 100644 --- a/src/zope/pagetemplate/engine.py +++ b/src/zope/pagetemplate/engine.py @@ -33,7 +33,7 @@ try: from zope.untrustedpython import rcompile from zope.untrustedpython.builtins import SafeBuiltins HAVE_UNTRUSTED = True -except ImportError: +except ImportError: # pragma: no cover HAVE_UNTRUSTED = False # PyPy doesn't support assigning to '__builtins__', even when @@ -41,7 +41,7 @@ except ImportError: # so don't try to use it. It won't work. if HAVE_UNTRUSTED: import platform - if platform.python_implementation() == 'PyPy': + if platform.python_implementation() == 'PyPy': # pragma: no cover HAVE_UNTRUSTED = False del rcompile del SafeBuiltins @@ -118,10 +118,15 @@ class ZopePythonExpr(PythonExpr): def _compile(self, text, filename): return rcompile.compile(text, filename, 'eval') +def _get_iinterpreter(): + from zope.app.interpreter.interfaces import IInterpreter + return IInterpreter # pragma: no cover class ZopeContextBase(Context): """Base class for both trusted and untrusted evaluation contexts.""" + request = None + def translate(self, msgid, domain=None, mapping=None, default=None): return translate(msgid, domain, mapping, context=self.request, default=default) @@ -131,24 +136,24 @@ class ZopeContextBase(Context): def evaluateCode(self, lang, code): if not self.evaluateInlineCode: raise InlineCodeError( - _('Inline Code Evaluation is deactivated, which means that ' - 'you cannot have inline code snippets in your Page ' - 'Template. Activate Inline Code Evaluation and try again.')) + _('Inline Code Evaluation is deactivated, which means that ' + 'you cannot have inline code snippets in your Page ' + 'Template. Activate Inline Code Evaluation and try again.')) # TODO This is only needed when self.evaluateInlineCode is true, # so should only be needed for zope.app.pythonpage. - from zope.app.interpreter.interfaces import IInterpreter + IInterpreter = _get_iinterpreter() interpreter = component.queryUtility(IInterpreter, lang) if interpreter is None: error = _('No interpreter named "${lang_name}" was found.', mapping={'lang_name': lang}) raise InlineCodeError(error) - globals = self.vars.copy() - result = interpreter.evaluateRawCode(code, globals) + globs = self.vars.copy() + result = interpreter.evaluateRawCode(code, globs) # Add possibly new global variables. old_names = self.vars.keys() - for name, value in globals.items(): + for name, value in globs.items(): if name not in old_names: self.setGlobal(name, value) return result @@ -466,7 +471,7 @@ class TraversableModuleImporter(SimpleModuleImporter): def traverse(self, name, further_path): try: return self[name] - except KeyError: + except ImportError: raise TraversalError(self, name) diff --git a/src/zope/pagetemplate/pagetemplate.py b/src/zope/pagetemplate/pagetemplate.py index b6bf8c2..9c05c89 100644 --- a/src/zope/pagetemplate/pagetemplate.py +++ b/src/zope/pagetemplate/pagetemplate.py @@ -30,8 +30,6 @@ from zope.pagetemplate.interfaces import IPageTemplateProgram from zope.interface import implementer from zope.interface import provider -from six import u as _u - _default_options = {} @@ -46,7 +44,7 @@ class StringIO(list): self.append(value) def getvalue(self): - return _u('').join(self) + return u''.join(self) @implementer(IPageTemplateSubclassing) @@ -87,12 +85,11 @@ class PageTemplate(object): _v_program = None _text = '' + @property def macros(self): self._cook_check() return self._v_macros - macros = property(macros) - def pt_edit(self, text, content_type): if content_type: self.content_type = str(content_type) @@ -105,7 +102,7 @@ class PageTemplate(object): 'options': options, 'args': args, 'nothing': None, - } + } rval.update(self.pt_getEngine().getBaseNames()) return rval @@ -145,15 +142,18 @@ class PageTemplate(object): if check_macro_expansion: try: self.pt_render(namespace, source=1) - except: + except Exception: return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2]) def _convert(self, string, text): """Adjust the string type to the type of text""" - if isinstance(text, six.binary_type): + if isinstance(text, six.binary_type) and not isinstance(string, six.binary_type): return string.encode('utf-8') - else: - return string + + if isinstance(text, six.text_type) and not isinstance(string, six.text_type): + return string.decode('utf-8') + + return string def write(self, text): # We accept both, since the text can either come from a file (and the @@ -252,7 +252,7 @@ class PageTemplateEngine(object): self.program = program def __call__(self, context, macros, **options): - output = StringIO(_u('')) + output = StringIO(u'') interpreter = TALInterpreter( self.program, macros, context, stream=output, **options diff --git a/src/zope/pagetemplate/pagetemplatefile.py b/src/zope/pagetemplate/pagetemplatefile.py index cee1c07..4cf0bf3 100644 --- a/src/zope/pagetemplate/pagetemplatefile.py +++ b/src/zope/pagetemplate/pagetemplatefile.py @@ -25,6 +25,8 @@ import logging from zope.pagetemplate.pagetemplate import PageTemplate +logger = logging.getLogger(__name__) + DEFAULT_ENCODING = "utf-8" meta_pattern = re.compile( @@ -40,10 +42,7 @@ class PageTemplateFile(PageTemplate): "Zope wrapper for filesystem Page Template using TAL, TALES, and METAL" _v_last_read = 0 - - #_error_start = b'' - #_newline = b'\n' + _v_debug = __debug__ def __init__(self, filename, _prefix=None): path = self.get_path_from_prefix(_prefix) @@ -75,21 +74,18 @@ class PageTemplateFile(PageTemplate): def _read_file(self): __traceback_info__ = self.filename - f = open(self.filename, "rb") - try: + with open(self.filename, "rb") as f: text = f.read(XML_PREFIX_MAX_LENGTH) - except: - f.close() - raise - type_ = sniff_type(text) - text += f.read() + type_ = sniff_type(text) + text += f.read() + if type_ != "text/xml": text, type_ = self._prepare_html(text) - f.close() + return text, type_ def _cook_check(self): - if self._v_last_read and not __debug__: + if self._v_last_read and not self._v_debug: return __traceback_info__ = self.filename try: @@ -102,8 +98,8 @@ class PageTemplateFile(PageTemplate): self.pt_edit(text, type_) assert self._v_cooked if self._v_errors: - logging.error('PageTemplateFile: Error in template %s: %s', - self.filename, '\n'.join(self._v_errors)) + logger.error('PageTemplateFile: Error in template %s: %s', + self.filename, '\n'.join(self._v_errors)) return self._v_last_read = mtime diff --git a/src/zope/pagetemplate/tests/batch.py b/src/zope/pagetemplate/tests/batch.py index 45f3ad9..5d193bd 100644 --- a/src/zope/pagetemplate/tests/batch.py +++ b/src/zope/pagetemplate/tests/batch.py @@ -20,97 +20,52 @@ class batch(object): def __init__(self, sequence, size, start=0, end=0, orphan=3, overlap=0): - start=start+1 + start = start + 1 - start,end,sz=opt(start,end,size,orphan,sequence) + start, end, sz = opt(start, end, size, orphan, sequence) - self._last=end-1 - self._first=start-1 + self._last = end - 1 + self._first = start - 1 - self._sequence=sequence - self._size=size - self._start=start - self._end=end - self._orphan=orphan - self._overlap=overlap + self._sequence = sequence + self._size = size + self._start = start + self._end = end + self._orphan = orphan + self._overlap = overlap - def previous_sequence(self): return self._first - - def previous_sequence_end_number(self): - start,end,spam=opt(0, self._start-1+self._overlap, - self._size, self._orphan, self._sequence) - return end - - def previous_sequence_start_number(self): - start,end,spam=opt(0, self._start-1+self._overlap, - self._size, self._orphan, self._sequence) - return start - - def previous_sequence_end_item(self): - start,end,spam=opt(0, self._start-1+self._overlap, - self._size, self._orphan, self._sequence) - return self._sequence[end-1] - - def previous_sequence_start_item(self): - start,end,spam=opt(0, self._start-1+self._overlap, - self._size, self._orphan, self._sequence) - return self._sequence[start-1] - - def next_sequence_end_number(self): - start,end,spam=opt(self._end+1-self._overlap, 0, - self._size, self._orphan, self._sequence) - return end - - def next_sequence_start_number(self): - start,end,spam=opt(self._end+1-self._overlap, 0, - self._size, self._orphan, self._sequence) - return start + def previous_sequence(self): + return self._first def next_sequence_end_item(self): - start,end,spam=opt(self._end+1-self._overlap, 0, - self._size, self._orphan, self._sequence) + _start, end, _spam = opt(self._end+1-self._overlap, 0, + self._size, self._orphan, self._sequence) return self._sequence[end-1] def next_sequence_start_item(self): - start,end,spam=opt(self._end+1-self._overlap, 0, - self._size, self._orphan, self._sequence) + start, _end, _spam = opt(self._end+1-self._overlap, 0, + self._size, self._orphan, self._sequence) return self._sequence[start-1] - def next_sequence(self): - try: self._sequence[self._end] - except IndexError: return 0 - else: return 1 + return self._end < len(self._sequence) + # try: self._sequence[self._end] + # except IndexError: return 0 + # else: return 1 def __getitem__(self, index): - if index > self._last: raise IndexError(index) - return self._sequence[index+self._first] + if index > self._last: + raise IndexError(index) + return self._sequence[index + self._first] -def opt(start,end,size,orphan,sequence): - if size < 1: - if start > 0 and end > 0 and end >= start: - size=end+1-start - else: size=7 +def opt(start, end, size, orphan, sequence): + assert size >= 1 + assert start > 0 - if start > 0: + start = len(sequence) if start - 1 >= len(sequence) else start + assert end <= 0 + end = start + size - 1 - try: sequence[start-1] - except: start=len(sequence) + assert end + orphan - 1 < len(sequence) - if end > 0: - if end < start: end=start - else: - end=start+size-1 - try: sequence[end+orphan-1] - except: end=len(sequence) - elif end > 0: - try: sequence[end-1] - except: end=len(sequence) - start=end+1-size - if start - 1 < orphan: start=1 - else: - start=1 - end=start+size-1 - try: sequence[end+orphan-1] - except: end=len(sequence) - return start,end,size + return start, end, size diff --git a/src/zope/pagetemplate/tests/test_basictemplate.py b/src/zope/pagetemplate/tests/test_basictemplate.py index 430a901..aa275a4 100644 --- a/src/zope/pagetemplate/tests/test_basictemplate.py +++ b/src/zope/pagetemplate/tests/test_basictemplate.py @@ -15,8 +15,6 @@ """ import unittest -from six import u as _u - from zope.pagetemplate.tests import util import zope.pagetemplate.pagetemplate import zope.component.testing @@ -32,26 +30,25 @@ class BasicTemplateTests(unittest.TestCase): def test_if_in_var(self): # DTML test 1: if, in, and var: - pass # for unittest - """ - %(comment)[ blah %(comment)] - Test of documentation templates - - %(if args)[ -
The arguments to this test program were:

-

-
    - %(in args)[ -
  • Argument number %(num)d was %(arg)s - %(in args)] -

- %(if args)] - %(else args)[ - No arguments were given.

- %(else args)] - And thats da trooth. - - """ + # """ + # %(comment)[ blah %(comment)] + # Test of documentation templates + # + # %(if args)[ + #

The arguments to this test program were:

+ #

+ #
    + # %(in args)[ + #
  • Argument number %(num)d was %(arg)s + # %(in args)] + #

+ # %(if args)] + # %(else args)[ + # No arguments were given.

+ # %(else args)] + # And thats da trooth. + # + # """ tal = util.read_input('dtml1.html') self.t.write(tal) @@ -88,11 +85,11 @@ class BasicTemplateTests(unittest.TestCase): from zope.component import provideUtility class DummyProgram(object): - def __init__(*args): + def __init__(self, *args): self.args = args - def __call__(*args, **kwargs): - return self.args, args, kwargs + def __call__(self, *args, **kwargs): + return self.args, (self,) + args, kwargs class DummyEngine(object): @staticmethod @@ -102,19 +99,19 @@ class BasicTemplateTests(unittest.TestCase): provideUtility(DummyEngine, IPageTemplateEngine) self.t._cook() - self.assertTrue(isinstance(self.t._v_program, DummyProgram)) + self.assertIsInstance(self.t._v_program, DummyProgram) self.assertEqual(self.t._v_macros, "macros") # "Render" and unpack arguments passed for verification - ((cls, source_file, text, engine, content_type), - (program, context, macros), - options) = \ - self.t.pt_render({}) + ((source_file, text, _engine, content_type), + (program, _context, macros), + options) = self.t.pt_render({}) self.assertEqual(source_file, None) self.assertEqual(text, 'foo') self.assertEqual(content_type, 'text/html') - self.assertTrue(isinstance(program, DummyProgram)) + self.assertEqual(macros, 'macros') + self.assertIsInstance(program, DummyProgram) self.assertEqual(options, { 'tal': True, 'showtal': False, @@ -124,43 +121,43 @@ class BasicTemplateTests(unittest.TestCase): def test_batches_and_formatting(self): # DTML test 3: batches and formatting: - pass # for unittest - """ - Test of documentation templates - - - The arguments were: - - - (- - ) - - -

- -
.
-
Argument was
- - (- - ) - - -
- - No arguments were given.

- - And I\'m 100% sure! - - """ + # """ + # Test of documentation templates + # + # + # The arguments were: + # + # + # (- + # ) + # + # + #

+ # + #
.
+ #
Argument was
+ # + # (- + # ) + # + # + #
+ # + # No arguments were given.

+ # + # And I\'m 100% sure! + # + # """ tal = util.read_input('dtml3.html') self.t.write(tal) - aa = util.argv(('one', 'two', 'three', 'four', 'five', - 'six', 'seven', 'eight', 'nine', 'ten', - 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', - 'sixteen', 'seventeen', 'eighteen', 'nineteen', - 'twenty', - )) + aa = util.argv(( + 'one', 'two', 'three', 'four', 'five', + 'six', 'seven', 'eight', 'nine', 'ten', + 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', + 'sixteen', 'seventeen', 'eighteen', 'nineteen', + 'twenty', + )) from zope.pagetemplate.tests import batch o = self.t(content=aa, batch=batch.batch(aa.args, 5)) @@ -206,7 +203,7 @@ class BasicTemplateTests(unittest.TestCase): self.t() def test_unicode_html(self): - text = _u('

\xe4\xf6\xfc\xdf

') + text = u'

\xe4\xf6\xfc\xdf

' # test with HTML parser self.t.pt_edit(text, 'text/html') @@ -216,8 +213,65 @@ class BasicTemplateTests(unittest.TestCase): self.t.pt_edit(text, 'text/xml') self.assertEqual(self.t().strip(), text) -def test_suite(): - return unittest.makeSuite(BasicTemplateTests) + def test_edit_with_read(self): + from io import BytesIO + self.t.pt_edit(BytesIO(b""), None) + self.assertEqual(self.t._text, b'') + + def test_errors(self): + self.t._v_cooked = True + self.t._v_errors = 1 + e = self.t.pt_errors(None) + self.assertEqual(e, 1) + + self.t._v_errors = () + e = self.t.pt_errors(None) + self.assertEqual(e[0], 'Macro expansion failed') + + def test_convert(self): + string = u'binary' + text = b'binary' + self.assertEqual(text, self.t._convert(string, text)) + + def test_write_error(self): + self.t.write(self.t._error_start + 'stuff' + self.t._error_end + self.t._newline) + self.assertEqual(self.t._text, '') + + def test_read_no_expand(self): + self.t.expand = False + self.t._text = self + self.t._v_cooked = True + + self.assertIs(self.t.read(), self) + + def test_read_error_expand(self): + self.t.expand = True + self.t._text = '' + self.t._v_cooked = True + text = self.t.read() + self.assertIn(self.t._error_start, text) + self.assertIn("Macro expansion failed", text) + + + def test_macros(self): + self.assertEqual(self.t.macros, {}) + + +class TestPageTemplateTracebackSupplement(unittest.TestCase): + + def test_errors_old_style(self): + class PT(object): + def pt_errors(self, ns): + return (ns,) + + pts = zope.pagetemplate.pagetemplate.PageTemplateTracebackSupplement(PT(), 'ns') + + self.assertEqual(pts.warnings, ['ns']) + + def test_errors_none(self): + class PT(object): + def pt_errors(self, ns, check_macro_expansion=False): + return None -if __name__ == '__main__': - unittest.TextTestRunner().run(test_suite()) + pts = zope.pagetemplate.pagetemplate.PageTemplateTracebackSupplement(PT(), 'ns') + self.assertEqual(pts.warnings, []) diff --git a/src/zope/pagetemplate/tests/test_engine.py b/src/zope/pagetemplate/tests/test_engine.py index ab3b5ca..ab61408 100644 --- a/src/zope/pagetemplate/tests/test_engine.py +++ b/src/zope/pagetemplate/tests/test_engine.py @@ -18,34 +18,29 @@ import re import unittest import zope.pagetemplate.engine from zope.testing.renormalizing import RENormalizing +from zope.component.testing import PlacelessSetup +class EngineTests(PlacelessSetup, + unittest.TestCase): -class DummyNamespace(object): - - def __init__(self, context): - self.context = context - -class EngineTests(unittest.TestCase): - - def setUp(self): - from zope.component.testing import setUp - setUp() - - def tearDown(self): - from zope.component.testing import tearDown - tearDown() + def _makeOne(self): + return zope.pagetemplate.engine._Engine() def test_function_namespaces_return_secured_proxies(self): # See https://bugs.launchpad.net/zope3/+bug/98323 - from zope.component import provideAdapter - from zope.traversing.interfaces import IPathAdapter - from zope.pagetemplate.engine import _Engine from zope.proxy import isProxy - provideAdapter(DummyNamespace, (None,), IPathAdapter, name='test') - engine = _Engine() + engine = self._makeOne() namespace = engine.getFunctionNamespace('test') self.assertTrue(isProxy(namespace)) + def test_getContext_namespace(self): + engine = self._makeOne() + ctx = engine.getContext({'a': 1}, b=2, request=3, context=4) + self.assertEqual(ctx.getValue('a'), 1) + self.assertEqual(ctx.getValue('b'), 2) + self.assertEqual(ctx.getValue('request'), 3) + self.assertEqual(ctx.getValue('context'), 4) + class DummyEngine(object): def getTypes(self): @@ -71,6 +66,8 @@ class ZopePythonExprTests(unittest.TestCase): DummyEngine()) self.assertEqual(expr(DummyContext()), 'sys') + @unittest.skipUnless(zope.pagetemplate.engine.HAVE_UNTRUSTED, + "Needs untrusted") def test_forbidden_module_name(self): from zope.pagetemplate.engine import ZopePythonExpr from zope.security.interfaces import Forbidden @@ -78,12 +75,110 @@ class ZopePythonExprTests(unittest.TestCase): DummyEngine()) self.assertRaises(Forbidden, expr, DummyContext()) + @unittest.skipUnless(zope.pagetemplate.engine.HAVE_UNTRUSTED, + "Needs untrusted") def test_disallowed_builtin(self): from zope.pagetemplate.engine import ZopePythonExpr expr = ZopePythonExpr('python', 'open("x", "w")', DummyEngine()) self.assertRaises(NameError, expr, DummyContext()) +class TestZopeContext(PlacelessSetup, + unittest.TestCase): + + def _makeOne(self): + return zope.pagetemplate.engine.ZopeContext(None, {}) + + def test_translate(self): + ctx = self._makeOne() + self.assertEqual(ctx.translate('msgid'), 'msgid') + + def test_evaluate_error(self): + ctx = self._makeOne() + with self.assertRaisesRegexp(zope.pagetemplate.engine.InlineCodeError, + "Inline Code Evaluation is deactivated"): + ctx.evaluateCode('lang', 'code') + + def test_evaluate_interpreter_not_importable(self): + ctx = self._makeOne() + ctx.evaluateInlineCode = True + with self.assertRaises(ImportError): + ctx.evaluateCode('lang', 'code') + + def test_evaluate_interpreter_not_found(self): + get = zope.pagetemplate.engine._get_iinterpreter + from zope import interface + class IInterpreter(interface.Interface): + pass + def mock_get(): + return IInterpreter + + ctx = self._makeOne() + ctx.evaluateInlineCode = True + zope.pagetemplate.engine._get_iinterpreter = mock_get + try: + with self.assertRaisesRegexp(zope.pagetemplate.engine.InlineCodeError, + "No interpreter named"): + ctx.evaluateCode('lang', 'code') + finally: + zope.pagetemplate.engine._get_iinterpreter = get + + def test_evaluate_interpreter_found(self): + get = zope.pagetemplate.engine._get_iinterpreter + from zope import interface + from zope import component + class IInterpreter(interface.Interface): + pass + def mock_get(): + return IInterpreter + + @interface.implementer(IInterpreter) + class Interpreter(object): + def evaluateRawCode(self, code, globs): + globs['new'] = code + return 42 + + component.provideUtility(Interpreter(), name='lang') + + ctx = self._makeOne() + ctx.evaluateInlineCode = True + zope.pagetemplate.engine._get_iinterpreter = mock_get + try: + result = ctx.evaluateCode('lang', 'code') + finally: + zope.pagetemplate.engine._get_iinterpreter = get + + self.assertEqual(result, 42) + self.assertEqual('code', ctx.getValue('new')) + + +class TestTraversableModuleImporter(unittest.TestCase): + + def test_traverse_fails(self): + from zope.traversing.interfaces import TraversalError + + tmi = zope.pagetemplate.engine.TraversableModuleImporter() + with self.assertRaises(TraversalError): + tmi.traverse('zope.cannot exist', ()) + + with self.assertRaises(TraversalError): + tmi.traverse('zope.pagetemplate.engine.DNE', ()) + + + with self.assertRaises(TraversalError): + tmi.traverse('pickle.no_sub_module', ()) + + +class TestAppPT(unittest.TestCase): + + def test_apppt_engine(self): + self.assertIs(zope.pagetemplate.engine.AppPT().pt_getEngine(), + zope.pagetemplate.engine.Engine) + + def test_trustedapppt_engine(self): + self.assertIs(zope.pagetemplate.engine.TrustedAppPT().pt_getEngine(), + zope.pagetemplate.engine.TrustedEngine) + def test_suite(): @@ -96,15 +191,12 @@ def test_suite(): (re.compile(r""), ""), # PyPy/pure-Python implementation (re.compile(r""), - ""), + ""), ]) - suite = unittest.TestSuite() + suite = unittest.defaultTestLoader.loadTestsFromName(__name__) suite.addTest(doctest.DocTestSuite('zope.pagetemplate.engine', checker=checker)) - suite.addTest(unittest.makeSuite(EngineTests)) - if zope.pagetemplate.engine.HAVE_UNTRUSTED: - suite.addTest(unittest.makeSuite(ZopePythonExprTests)) return suite diff --git a/src/zope/pagetemplate/tests/test_htmltests.py b/src/zope/pagetemplate/tests/test_htmltests.py index c6abd81..1550634 100644 --- a/src/zope/pagetemplate/tests/test_htmltests.py +++ b/src/zope/pagetemplate/tests/test_htmltests.py @@ -31,15 +31,17 @@ class HTMLTests(unittest.TestCase): def getProducts(self): return [ - {'description': 'This is the tee for those who LOVE Zope. ' - 'Show your heart on your tee.', - 'price': 12.99, 'image': 'smlatee.jpg' + { + 'description': ('This is the tee for those who LOVE Zope. ' + 'Show your heart on your tee.'), + 'price': 12.99, 'image': 'smlatee.jpg' }, - {'description': 'This is the tee for Jim Fulton. ' - 'He\'s the Zope Pope!', - 'price': 11.99, 'image': 'smpztee.jpg' + { + 'description': ('This is the tee for Jim Fulton. ' + 'He\'s the Zope Pope!'), + 'price': 11.99, 'image': 'smpztee.jpg' }, - ] + ] def test_1(self): laf = self.folder.laf @@ -53,7 +55,7 @@ class HTMLTests(unittest.TestCase): t = self.folder.t t.write(util.read_input('teeshop2.html')) expect = util.read_output('teeshop2.html') - out = t(laf = self.folder.laf, getProducts = self.getProducts) + out = t(laf=self.folder.laf, getProducts=self.getProducts) util.check_html(expect, out) @@ -63,7 +65,7 @@ class HTMLTests(unittest.TestCase): t = self.folder.t t.write(util.read_input('teeshop1.html')) expect = util.read_output('teeshop1.html') - out = t(laf = self.folder.laf, getProducts = self.getProducts) + out = t(laf=self.folder.laf, getProducts=self.getProducts) util.check_html(expect, out) def test_SimpleLoop(self): @@ -152,7 +154,4 @@ class HTMLTests(unittest.TestCase): self.assertFalse(errors) def test_suite(): - return unittest.makeSuite(HTMLTests) - -if __name__=='__main__': - unittest.TextTestRunner().run(test_suite()) + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/src/zope/pagetemplate/tests/test_ptfile.py b/src/zope/pagetemplate/tests/test_ptfile.py index d6ecbcd..e5cc2e9 100644 --- a/src/zope/pagetemplate/tests/test_ptfile.py +++ b/src/zope/pagetemplate/tests/test_ptfile.py @@ -20,23 +20,19 @@ import unittest import six from zope.pagetemplate.pagetemplatefile import PageTemplateFile +class AbstractPTCase(object): -class TypeSniffingTestCase(unittest.TestCase): - - TEMPFILENAME = tempfile.mktemp() - - def tearDown(self): - if os.path.exists(self.TEMPFILENAME): - os.unlink(self.TEMPFILENAME) - - def get_pt(self, text): - f = open(self.TEMPFILENAME, "wb") - f.write(text) - f.close() - pt = PageTemplateFile(self.TEMPFILENAME) + def get_pt(self, text=b''): + with tempfile.NamedTemporaryFile(mode='wb', delete=False) as f: + f.write(text) + self.addCleanup(os.unlink, f.name) + pt = PageTemplateFile(f.name) pt.read() return pt +class TypeSniffingTestCase(AbstractPTCase, + unittest.TestCase): + def check_content_type(self, text, expected_type): pt = self.get_pt(text) self.assertEqual(pt.content_type, expected_type) @@ -123,17 +119,18 @@ class TypeSniffingTestCase(unittest.TestCase): def test_sniffer_html_ascii(self): self.check_content_type( ("" - % self.HTML_SYSTEM_ID).encode("utf-8"), + % self.HTML_SYSTEM_ID).encode("utf-8"), "text/html") self.check_content_type( b"sample document", "text/html") - # TODO: This reflects a case that simply isn't handled by the - # sniffer; there are many, but it gets it right more often than - # before. - def donttest_sniffer_xml_simple(self): - self.check_content_type("", + @unittest.expectedFailure + def test_sniffer_xml_simple(self): + # TODO: This reflects a case that simply isn't handled by the + # sniffer; there are many, but it gets it right more often than + # before. This case actually returns text/html + self.check_content_type(b"", "text/xml") def test_html_default_encoding(self): @@ -145,9 +142,9 @@ class TypeSniffingTestCase(unittest.TestCase): rendered = pt() self.assertTrue(isinstance(rendered, six.text_type)) self.assertEqual(rendered.strip(), - six.u("" - "\u0422\u0435\u0441\u0442" - "")) + (u"" + u"\u0422\u0435\u0441\u0442" + u"")) def test_html_encoding_by_meta(self): pt = self.get_pt( @@ -160,9 +157,9 @@ class TypeSniffingTestCase(unittest.TestCase): rendered = pt() self.assertTrue(isinstance(rendered, six.text_type)) self.assertEqual(rendered.strip(), - six.u("" - "\u0422\u0435\u0441\u0442" - "")) + (u"" + u"\u0422\u0435\u0441\u0442" + u"")) def test_xhtml(self): pt = self.get_pt( @@ -175,14 +172,51 @@ class TypeSniffingTestCase(unittest.TestCase): rendered = pt() self.assertTrue(isinstance(rendered, six.text_type)) self.assertEqual(rendered.strip(), - six.u("" - "\u0422\u0435\u0441\u0442" - "")) + (u"" + u"\u0422\u0435\u0441\u0442" + u"")) + + +class TestPageTemplateFile(AbstractPTCase, + unittest.TestCase): + + def test_no_such_file(self): + with self.assertRaises(ValueError): + PageTemplateFile('this file does not exist') + + def test_prefix_str(self): + pt = PageTemplateFile(os.path.basename(__file__), + _prefix=os.path.dirname(__file__)) + self.assertEqual(pt.filename, __file__) + + + def test_cook_no_debug(self): + pt = self.get_pt() + pt._v_debug = False + pt._cook_check() + self.assertTrue(pt._v_last_read) + lr = pt._v_last_read + pt._cook_check() + self.assertEqual(lr, pt._v_last_read) + + + def test_cook_mtime_fails(self): + pt = self.get_pt() + getmtime = os.path.getmtime + def bad(_path): + raise OSError() + os.path.getmtime = bad + try: + pt._cook_check() + finally: + os.path.getmtime = getmtime + self.assertEqual(0, pt._v_last_read) -def test_suite(): - return unittest.makeSuite(TypeSniffingTestCase) + def test_pickle_not_allowed(self): + import pickle + pt = self.get_pt() -if __name__ == "__main__": - unittest.main(defaultTest="test_suite") + with self.assertRaises(TypeError): + pickle.dumps(pt) diff --git a/src/zope/pagetemplate/tests/util.py b/src/zope/pagetemplate/tests/util.py index 8dda99b..5ef2ad3 100644 --- a/src/zope/pagetemplate/tests/util.py +++ b/src/zope/pagetemplate/tests/util.py @@ -17,71 +17,46 @@ from __future__ import print_function import os import re import sys - - -class Bruce(object): - __allow_access_to_unprotected_subobjects__=1 - def __str__(self): return 'bruce' - def __int__(self): return 42 - def __float__(self): return 42.0 - def keys(self): return ['bruce']*7 - def values(self): return [self]*7 - def items(self): return [('bruce',self)]*7 - def __len__(self): return 7 - def __getitem__(self,index): - if isinstance(index, int) and (index < 0 or index > 6): - raise IndexError(index) - return self - isDocTemp = 0 - def __getattr__(self,name): - if name.startswith('_'): - raise AttributeError(name) - return self - -bruce = Bruce() +import unittest +import zope.pagetemplate.tests class arg(object): __allow_access_to_unprotected_subobjects__ = 1 - def __init__(self,nn,aa): self.num, self.arg = nn, aa - def __str__(self): return str(self.arg) + + def __init__(self, nn, aa): + self.num, self.arg = nn, aa + + def __str__(self): + return str(self.arg) class argv(object): __allow_access_to_unprotected_subobjects__ = 1 - def __init__(self, argv=sys.argv[1:]): + def __init__(self, argv=None): args = self.args = [] + argv = argv if argv is not None else sys.argv[1:] for aa in argv: - args.append(arg(len(args)+1,aa)) - - def items(self): - return map(lambda a: ('spam%d' % a.num, a), self.args) - - def values(self): return self.args - - def getRoot(self): - return self + args.append(arg(len(args) + 1, aa)) context = property(lambda self: self) -def nicerange(lo, hi): - if hi <= lo+1: - return str(lo+1) - else: - return "%d,%d" % (lo+1, hi) +class _Test(unittest.TestCase): + + def runTest(self): # pragma: no cover 2.7 compatibility + return -def dump(tag, x, lo, hi): - for i in xrange(lo, hi): - print('%s %s' % (tag, x[i]), end=' ') +_assertEqual = _Test().assertEqual +del _Test def check_html(s1, s2): s1 = normalize_html(s1) s2 = normalize_html(s2) - assert s1==s2, (s1, s2, "HTML Output Changed") + _assertEqual(s1, s2, "HTML Output Changed") def check_xml(s1, s2): s1 = normalize_xml(s1) s2 = normalize_xml(s2) - assert s1==s2, ("XML Output Changed:\n%r\n\n%r" % (s1, s2)) + _assertEqual(s1, s2, 'XML Output Changed') def normalize_html(s): s = re.sub(r"[ \t]+", " ", s) @@ -95,11 +70,11 @@ def normalize_xml(s): return s -import zope.pagetemplate.tests -dir = os.path.dirname(zope.pagetemplate.tests.__file__) -input_dir = os.path.join(dir, 'input') -output_dir = os.path.join(dir, 'output') + +here = os.path.dirname(zope.pagetemplate.tests.__file__) +input_dir = os.path.join(here, 'input') +output_dir = os.path.join(here, 'output') def read_input(filename): filename = os.path.join(input_dir, filename) diff --git a/tox.ini b/tox.ini index 0476758..90869b2 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ basepython = python2.7 commands = coverage run -m zope.testrunner --test-path=src [] - coverage report --fail-under=88 + coverage report --fail-under=100 deps = {[testenv]deps} coverage -- cgit v1.2.1