From cbd7133982496c1daf5c3e554841a5d66af58206 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 5 Apr 2012 07:54:05 -0700 Subject: Added to template_spec module docstring. --- pystache/template_spec.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/template_spec.py b/pystache/template_spec.py index c33f30b..76ce784 100644 --- a/pystache/template_spec.py +++ b/pystache/template_spec.py @@ -1,7 +1,11 @@ # coding: utf-8 """ -This module supports customized (aka special or specified) template loading. +Provides a class to customize template information on a per-view basis. + +To customize template properties for a particular view, create that view +from a class that subclasses TemplateSpec. The "Spec" in TemplateSpec +stands for template information that is "special" or "specified". """ -- cgit v1.2.1 From 15fbaa5358a7edc5f67d7e93f47cb037e4b5292c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 06:46:42 -0700 Subject: Moved tests to pystache/tests (intermediate step: tests not working yet). --- pystache/tests/__init__.py | 0 pystache/tests/benchmark.py | 94 +++++ pystache/tests/common.py | 65 +++ pystache/tests/data/__init__.py | 0 pystache/tests/data/ascii.mustache | 1 + pystache/tests/data/duplicate.mustache | 1 + pystache/tests/data/locator/duplicate.mustache | 1 + pystache/tests/data/non_ascii.mustache | 1 + pystache/tests/data/sample_view.mustache | 1 + pystache/tests/data/say_hello.mustache | 1 + pystache/tests/data/views.py | 16 + pystache/tests/test_commands.py | 45 ++ pystache/tests/test_context.py | 400 ++++++++++++++++++ pystache/tests/test_examples.py | 101 +++++ pystache/tests/test_loader.py | 198 +++++++++ pystache/tests/test_locator.py | 150 +++++++ pystache/tests/test_pystache.py | 116 ++++++ pystache/tests/test_renderengine.py | 455 ++++++++++++++++++++ pystache/tests/test_renderer.py | 556 +++++++++++++++++++++++++ pystache/tests/test_simple.py | 83 ++++ pystache/tests/test_spec.py | 130 ++++++ pystache/tests/test_template_spec.py | 388 +++++++++++++++++ 22 files changed, 2803 insertions(+) create mode 100644 pystache/tests/__init__.py create mode 100755 pystache/tests/benchmark.py create mode 100644 pystache/tests/common.py create mode 100644 pystache/tests/data/__init__.py create mode 100644 pystache/tests/data/ascii.mustache create mode 100644 pystache/tests/data/duplicate.mustache create mode 100644 pystache/tests/data/locator/duplicate.mustache create mode 100644 pystache/tests/data/non_ascii.mustache create mode 100644 pystache/tests/data/sample_view.mustache create mode 100644 pystache/tests/data/say_hello.mustache create mode 100644 pystache/tests/data/views.py create mode 100644 pystache/tests/test_commands.py create mode 100644 pystache/tests/test_context.py create mode 100644 pystache/tests/test_examples.py create mode 100644 pystache/tests/test_loader.py create mode 100644 pystache/tests/test_locator.py create mode 100644 pystache/tests/test_pystache.py create mode 100644 pystache/tests/test_renderengine.py create mode 100644 pystache/tests/test_renderer.py create mode 100644 pystache/tests/test_simple.py create mode 100644 pystache/tests/test_spec.py create mode 100644 pystache/tests/test_template_spec.py (limited to 'pystache') diff --git a/pystache/tests/__init__.py b/pystache/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pystache/tests/benchmark.py b/pystache/tests/benchmark.py new file mode 100755 index 0000000..d46e973 --- /dev/null +++ b/pystache/tests/benchmark.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" +A rudimentary backward- and forward-compatible script to benchmark pystache. + +Usage: + +tests/benchmark.py 10000 + +""" + +import sys +from timeit import Timer + +import pystache + +# TODO: make the example realistic. + +examples = [ + # Test case: 1 + ("""{{#person}}Hi {{name}}{{/person}}""", + {"person": {"name": "Jon"}}, + "Hi Jon"), + + # Test case: 2 + ("""\ +
+

{{header}}

+
    +{{#comments}}
  • +
    {{name}}

    {{body}}

    +
  • {{/comments}} +
+
""", + {'header': "My Post Comments", + 'comments': [ + {'name': "Joe", 'body': "Thanks for this post!"}, + {'name': "Sam", 'body': "Thanks for this post!"}, + {'name': "Heather", 'body': "Thanks for this post!"}, + {'name': "Kathy", 'body': "Thanks for this post!"}, + {'name': "George", 'body': "Thanks for this post!"}]}, + """\ +
+

My Post Comments

+
    +
  • +
    Joe

    Thanks for this post!

    +
  • +
    Sam

    Thanks for this post!

    +
  • +
    Heather

    Thanks for this post!

    +
  • +
    Kathy

    Thanks for this post!

    +
  • +
    George

    Thanks for this post!

    +
  • +
+
"""), +] + + +def make_test_function(example): + + template, context, expected = example + + def test(): + actual = pystache.render(template, context) + if actual != expected: + raise Exception("Benchmark mismatch: \n%s\n*** != ***\n%s" % (expected, actual)) + + return test + + +def main(sys_argv): + args = sys_argv[1:] + count = int(args[0]) + + print "Benchmarking: %sx" % count + print + + for example in examples: + + test = make_test_function(example) + + t = Timer(test,) + print min(t.repeat(repeat=3, number=count)) + + print "Done" + + +if __name__ == '__main__': + main(sys.argv) + diff --git a/pystache/tests/common.py b/pystache/tests/common.py new file mode 100644 index 0000000..c5aa587 --- /dev/null +++ b/pystache/tests/common.py @@ -0,0 +1,65 @@ +# coding: utf-8 + +""" +Provides test-related code that can be used by all tests. + +""" + +import os + +import examples + + +DATA_DIR = 'tests/data' +EXAMPLES_DIR = os.path.dirname(examples.__file__) + + +def get_data_path(file_name): + return os.path.join(DATA_DIR, file_name) + + +class AssertStringMixin: + + """A unittest.TestCase mixin to check string equality.""" + + def assertString(self, actual, expected, format=None): + """ + Assert that the given strings are equal and have the same type. + + Arguments: + + format: a format string containing a single conversion specifier %s. + Defaults to "%s". + + """ + if format is None: + format = "%s" + + # Show both friendly and literal versions. + details = """String mismatch: %%s\ + + + Expected: \"""%s\""" + Actual: \"""%s\""" + + Expected: %s + Actual: %s""" % (expected, actual, repr(expected), repr(actual)) + + def make_message(reason): + description = details % reason + return format % description + + self.assertEquals(actual, expected, make_message("different characters")) + + reason = "types different: %s != %s (actual)" % (repr(type(expected)), repr(type(actual))) + self.assertEquals(type(expected), type(actual), make_message(reason)) + + +class AssertIsMixin: + + """A unittest.TestCase mixin adding assertIs().""" + + # unittest.assertIs() is not available until Python 2.7: + # http://docs.python.org/library/unittest.html#unittest.TestCase.assertIsNone + def assertIs(self, first, second): + self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second))) diff --git a/pystache/tests/data/__init__.py b/pystache/tests/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pystache/tests/data/ascii.mustache b/pystache/tests/data/ascii.mustache new file mode 100644 index 0000000..e86737b --- /dev/null +++ b/pystache/tests/data/ascii.mustache @@ -0,0 +1 @@ +ascii: abc \ No newline at end of file diff --git a/pystache/tests/data/duplicate.mustache b/pystache/tests/data/duplicate.mustache new file mode 100644 index 0000000..a0515e3 --- /dev/null +++ b/pystache/tests/data/duplicate.mustache @@ -0,0 +1 @@ +This file is used to test locate_path()'s search order. \ No newline at end of file diff --git a/pystache/tests/data/locator/duplicate.mustache b/pystache/tests/data/locator/duplicate.mustache new file mode 100644 index 0000000..a0515e3 --- /dev/null +++ b/pystache/tests/data/locator/duplicate.mustache @@ -0,0 +1 @@ +This file is used to test locate_path()'s search order. \ No newline at end of file diff --git a/pystache/tests/data/non_ascii.mustache b/pystache/tests/data/non_ascii.mustache new file mode 100644 index 0000000..bd69b61 --- /dev/null +++ b/pystache/tests/data/non_ascii.mustache @@ -0,0 +1 @@ +non-ascii: é \ No newline at end of file diff --git a/pystache/tests/data/sample_view.mustache b/pystache/tests/data/sample_view.mustache new file mode 100644 index 0000000..e86737b --- /dev/null +++ b/pystache/tests/data/sample_view.mustache @@ -0,0 +1 @@ +ascii: abc \ No newline at end of file diff --git a/pystache/tests/data/say_hello.mustache b/pystache/tests/data/say_hello.mustache new file mode 100644 index 0000000..84ab4c9 --- /dev/null +++ b/pystache/tests/data/say_hello.mustache @@ -0,0 +1 @@ +Hello, {{to}} \ No newline at end of file diff --git a/pystache/tests/data/views.py b/pystache/tests/data/views.py new file mode 100644 index 0000000..4d9df02 --- /dev/null +++ b/pystache/tests/data/views.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +from pystache import TemplateSpec + +class SayHello(object): + + def to(self): + return "World" + + +class SampleView(TemplateSpec): + pass + + +class NonAscii(TemplateSpec): + pass diff --git a/pystache/tests/test_commands.py b/pystache/tests/test_commands.py new file mode 100644 index 0000000..f1817e7 --- /dev/null +++ b/pystache/tests/test_commands.py @@ -0,0 +1,45 @@ +# coding: utf-8 + +""" +Unit tests of commands.py. + +""" + +import sys +import unittest + +from pystache.commands import main + + +ORIGINAL_STDOUT = sys.stdout + + +class MockStdout(object): + + def __init__(self): + self.output = "" + + def write(self, str): + self.output += str + + +class CommandsTestCase(unittest.TestCase): + + def setUp(self): + sys.stdout = MockStdout() + + def callScript(self, template, context): + argv = ['pystache', template, context] + main(argv) + return sys.stdout.output + + def testMainSimple(self): + """ + Test a simple command-line case. + + """ + actual = self.callScript("Hi {{thing}}", '{"thing": "world"}') + self.assertEquals(actual, u"Hi world\n") + + def tearDown(self): + sys.stdout = ORIGINAL_STDOUT diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py new file mode 100644 index 0000000..decf4fb --- /dev/null +++ b/pystache/tests/test_context.py @@ -0,0 +1,400 @@ +# coding: utf-8 + +""" +Unit tests of context.py. + +""" + +from datetime import datetime +import unittest + +from pystache.context import _NOT_FOUND +from pystache.context import _get_value +from pystache.context import Context +from tests.common import AssertIsMixin + +class SimpleObject(object): + + """A sample class that does not define __getitem__().""" + + def __init__(self): + self.foo = "bar" + + def foo_callable(self): + return "called..." + + +class DictLike(object): + + """A sample class that implements __getitem__() and __contains__().""" + + def __init__(self): + self._dict = {'foo': 'bar'} + self.fuzz = 'buzz' + + def __contains__(self, key): + return key in self._dict + + def __getitem__(self, key): + return self._dict[key] + + +class GetValueTests(unittest.TestCase, AssertIsMixin): + + """Test context._get_value().""" + + def assertNotFound(self, item, key): + """ + Assert that a call to _get_value() returns _NOT_FOUND. + + """ + self.assertIs(_get_value(item, key), _NOT_FOUND) + + ### Case: the item is a dictionary. + + def test_dictionary__key_present(self): + """ + Test getting a key from a dictionary. + + """ + item = {"foo": "bar"} + self.assertEquals(_get_value(item, "foo"), "bar") + + def test_dictionary__callable_not_called(self): + """ + Test that callable values are returned as-is (and in particular not called). + + """ + def foo_callable(self): + return "bar" + + item = {"foo": foo_callable} + self.assertNotEquals(_get_value(item, "foo"), "bar") + self.assertTrue(_get_value(item, "foo") is foo_callable) + + def test_dictionary__key_missing(self): + """ + Test getting a missing key from a dictionary. + + """ + item = {} + self.assertNotFound(item, "missing") + + def test_dictionary__attributes_not_checked(self): + """ + Test that dictionary attributes are not checked. + + """ + item = {} + attr_name = "keys" + self.assertEquals(getattr(item, attr_name)(), []) + self.assertNotFound(item, attr_name) + + def test_dictionary__dict_subclass(self): + """ + Test that subclasses of dict are treated as dictionaries. + + """ + class DictSubclass(dict): pass + + item = DictSubclass() + item["foo"] = "bar" + + self.assertEquals(_get_value(item, "foo"), "bar") + + ### Case: the item is an object. + + def test_object__attribute_present(self): + """ + Test getting an attribute from an object. + + """ + item = SimpleObject() + self.assertEquals(_get_value(item, "foo"), "bar") + + def test_object__attribute_missing(self): + """ + Test getting a missing attribute from an object. + + """ + item = SimpleObject() + self.assertNotFound(item, "missing") + + def test_object__attribute_is_callable(self): + """ + Test getting a callable attribute from an object. + + """ + item = SimpleObject() + self.assertEquals(_get_value(item, "foo_callable"), "called...") + + def test_object__non_built_in_type(self): + """ + Test getting an attribute from an instance of a type that isn't built-in. + + """ + item = datetime(2012, 1, 2) + self.assertEquals(_get_value(item, "day"), 2) + + def test_object__dict_like(self): + """ + Test getting a key from a dict-like object (an object that implements '__getitem__'). + + """ + item = DictLike() + self.assertEquals(item["foo"], "bar") + self.assertNotFound(item, "foo") + + ### Case: the item is an instance of a built-in type. + + def test_built_in_type__integer(self): + """ + Test getting from an integer. + + """ + class MyInt(int): pass + + item1 = MyInt(10) + item2 = 10 + + try: + item2.real + except AttributeError: + # Then skip this unit test. The numeric type hierarchy was + # added only in Python 2.6, in which case integers inherit + # from complex numbers the "real" attribute, etc: + # + # http://docs.python.org/library/numbers.html + # + return + + self.assertEquals(item1.real, 10) + self.assertEquals(item2.real, 10) + + self.assertEquals(_get_value(item1, 'real'), 10) + self.assertNotFound(item2, 'real') + + def test_built_in_type__string(self): + """ + Test getting from a string. + + """ + class MyStr(str): pass + + item1 = MyStr('abc') + item2 = 'abc' + + self.assertEquals(item1.upper(), 'ABC') + self.assertEquals(item2.upper(), 'ABC') + + self.assertEquals(_get_value(item1, 'upper'), 'ABC') + self.assertNotFound(item2, 'upper') + + def test_built_in_type__list(self): + """ + Test getting from a list. + + """ + class MyList(list): pass + + item1 = MyList([1, 2, 3]) + item2 = [1, 2, 3] + + self.assertEquals(item1.pop(), 3) + self.assertEquals(item2.pop(), 3) + + self.assertEquals(_get_value(item1, 'pop'), 2) + self.assertNotFound(item2, 'pop') + + +class ContextTests(unittest.TestCase, AssertIsMixin): + + """ + Test the Context class. + + """ + + def test_init__no_elements(self): + """ + Check that passing nothing to __init__() raises no exception. + + """ + context = Context() + + def test_init__many_elements(self): + """ + Check that passing more than two items to __init__() raises no exception. + + """ + context = Context({}, {}, {}) + + def test__repr(self): + context = Context() + self.assertEquals(repr(context), 'Context()') + + context = Context({'foo': 'bar'}) + self.assertEquals(repr(context), "Context({'foo': 'bar'},)") + + context = Context({'foo': 'bar'}, {'abc': 123}) + self.assertEquals(repr(context), "Context({'foo': 'bar'}, {'abc': 123})") + + def test__str(self): + context = Context() + self.assertEquals(str(context), 'Context()') + + context = Context({'foo': 'bar'}) + self.assertEquals(str(context), "Context({'foo': 'bar'},)") + + context = Context({'foo': 'bar'}, {'abc': 123}) + self.assertEquals(str(context), "Context({'foo': 'bar'}, {'abc': 123})") + + ## Test the static create() method. + + def test_create__dictionary(self): + """ + Test passing a dictionary. + + """ + context = Context.create({'foo': 'bar'}) + self.assertEquals(context.get('foo'), 'bar') + + def test_create__none(self): + """ + Test passing None. + + """ + context = Context.create({'foo': 'bar'}, None) + self.assertEquals(context.get('foo'), 'bar') + + def test_create__object(self): + """ + Test passing an object. + + """ + class Foo(object): + foo = 'bar' + context = Context.create(Foo()) + self.assertEquals(context.get('foo'), 'bar') + + def test_create__context(self): + """ + Test passing a Context instance. + + """ + obj = Context({'foo': 'bar'}) + context = Context.create(obj) + self.assertEquals(context.get('foo'), 'bar') + + def test_create__kwarg(self): + """ + Test passing a keyword argument. + + """ + context = Context.create(foo='bar') + self.assertEquals(context.get('foo'), 'bar') + + def test_create__precedence_positional(self): + """ + Test precedence of positional arguments. + + """ + context = Context.create({'foo': 'bar'}, {'foo': 'buzz'}) + self.assertEquals(context.get('foo'), 'buzz') + + def test_create__precedence_keyword(self): + """ + Test precedence of keyword arguments. + + """ + context = Context.create({'foo': 'bar'}, foo='buzz') + self.assertEquals(context.get('foo'), 'buzz') + + def test_get__key_present(self): + """ + Test getting a key. + + """ + context = Context({"foo": "bar"}) + self.assertEquals(context.get("foo"), "bar") + + def test_get__key_missing(self): + """ + Test getting a missing key. + + """ + context = Context() + self.assertTrue(context.get("foo") is None) + + def test_get__default(self): + """ + Test that get() respects the default value. + + """ + context = Context() + self.assertEquals(context.get("foo", "bar"), "bar") + + def test_get__precedence(self): + """ + Test that get() respects the order of precedence (later items first). + + """ + context = Context({"foo": "bar"}, {"foo": "buzz"}) + self.assertEquals(context.get("foo"), "buzz") + + def test_get__fallback(self): + """ + Check that first-added stack items are queried on context misses. + + """ + context = Context({"fuzz": "buzz"}, {"foo": "bar"}) + self.assertEquals(context.get("fuzz"), "buzz") + + def test_push(self): + """ + Test push(). + + """ + key = "foo" + context = Context({key: "bar"}) + self.assertEquals(context.get(key), "bar") + + context.push({key: "buzz"}) + self.assertEquals(context.get(key), "buzz") + + def test_pop(self): + """ + Test pop(). + + """ + key = "foo" + context = Context({key: "bar"}, {key: "buzz"}) + self.assertEquals(context.get(key), "buzz") + + item = context.pop() + self.assertEquals(item, {"foo": "buzz"}) + self.assertEquals(context.get(key), "bar") + + def test_top(self): + key = "foo" + context = Context({key: "bar"}, {key: "buzz"}) + self.assertEquals(context.get(key), "buzz") + + top = context.top() + self.assertEquals(top, {"foo": "buzz"}) + # Make sure calling top() didn't remove the item from the stack. + self.assertEquals(context.get(key), "buzz") + + def test_copy(self): + key = "foo" + original = Context({key: "bar"}, {key: "buzz"}) + self.assertEquals(original.get(key), "buzz") + + new = original.copy() + # Confirm that the copy behaves the same. + self.assertEquals(new.get(key), "buzz") + # Change the copy, and confirm it is changed. + new.pop() + self.assertEquals(new.get(key), "bar") + # Confirm the original is unchanged. + self.assertEquals(original.get(key), "buzz") + diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py new file mode 100644 index 0000000..179b089 --- /dev/null +++ b/pystache/tests/test_examples.py @@ -0,0 +1,101 @@ +# encoding: utf-8 + +import unittest + +from examples.comments import Comments +from examples.double_section import DoubleSection +from examples.escaped import Escaped +from examples.unescaped import Unescaped +from examples.template_partial import TemplatePartial +from examples.delimiters import Delimiters +from examples.unicode_output import UnicodeOutput +from examples.unicode_input import UnicodeInput +from examples.nested_context import NestedContext +from pystache import Renderer +from tests.common import EXAMPLES_DIR +from tests.common import AssertStringMixin + + +class TestView(unittest.TestCase, AssertStringMixin): + + def _assert(self, obj, expected): + renderer = Renderer() + actual = renderer.render(obj) + self.assertString(actual, expected) + + def test_comments(self): + self._assert(Comments(), u"

A Comedy of Errors

") + + def test_double_section(self): + self._assert(DoubleSection(), u"* first\n* second\n* third") + + def test_unicode_output(self): + renderer = Renderer() + actual = renderer.render(UnicodeOutput()) + self.assertString(actual, u'

Name: Henri Poincaré

') + + def test_unicode_input(self): + renderer = Renderer() + actual = renderer.render(UnicodeInput()) + self.assertString(actual, u'abcdé') + + def test_escaping(self): + self._assert(Escaped(), u"

Bear > Shark

") + + def test_literal(self): + renderer = Renderer() + actual = renderer.render(Unescaped()) + self.assertString(actual, u"

Bear > Shark

") + + def test_template_partial(self): + renderer = Renderer(search_dirs=EXAMPLES_DIR) + actual = renderer.render(TemplatePartial(renderer=renderer)) + + self.assertString(actual, u"""

Welcome

+Again, Welcome!""") + + def test_template_partial_extension(self): + renderer = Renderer(search_dirs=EXAMPLES_DIR, file_extension='txt') + + view = TemplatePartial(renderer=renderer) + + actual = renderer.render(view) + self.assertString(actual, u"""Welcome +------- + +## Again, Welcome! ##""") + + def test_delimiters(self): + renderer = Renderer() + actual = renderer.render(Delimiters()) + self.assertString(actual, u"""\ +* It worked the first time. +* And it worked the second time. +* Then, surprisingly, it worked the third time. +""") + + def test_nested_context(self): + renderer = Renderer() + actual = renderer.render(NestedContext(renderer)) + self.assertString(actual, u"one and foo and two") + + def test_nested_context_is_available_in_view(self): + renderer = Renderer() + + view = NestedContext(renderer) + view.template = '{{#herp}}{{#derp}}{{nested_context_in_view}}{{/derp}}{{/herp}}' + + actual = renderer.render(view) + self.assertString(actual, u'it works!') + + def test_partial_in_partial_has_access_to_grand_parent_context(self): + renderer = Renderer(search_dirs=EXAMPLES_DIR) + + view = TemplatePartial(renderer=renderer) + view.template = '''{{>partial_in_partial}}''' + + actual = renderer.render(view, {'prop': 'derp'}) + self.assertEquals(actual, 'Hi derp!') + +if __name__ == '__main__': + unittest.main() diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py new file mode 100644 index 0000000..119ebef --- /dev/null +++ b/pystache/tests/test_loader.py @@ -0,0 +1,198 @@ +# encoding: utf-8 + +""" +Unit tests of reader.py. + +""" + +import os +import sys +import unittest + +from tests.common import AssertStringMixin +from pystache import defaults +from pystache.loader import Loader + + +DATA_DIR = 'tests/data' + + +class LoaderTests(unittest.TestCase, AssertStringMixin): + + def test_init__extension(self): + loader = Loader(extension='foo') + self.assertEquals(loader.extension, 'foo') + + def test_init__extension__default(self): + # Test the default value. + loader = Loader() + self.assertEquals(loader.extension, 'mustache') + + def test_init__file_encoding(self): + loader = Loader(file_encoding='bar') + self.assertEquals(loader.file_encoding, 'bar') + + def test_init__file_encoding__default(self): + file_encoding = defaults.FILE_ENCODING + try: + defaults.FILE_ENCODING = 'foo' + loader = Loader() + self.assertEquals(loader.file_encoding, 'foo') + finally: + defaults.FILE_ENCODING = file_encoding + + def test_init__to_unicode(self): + to_unicode = lambda x: x + loader = Loader(to_unicode=to_unicode) + self.assertEquals(loader.to_unicode, to_unicode) + + def test_init__to_unicode__default(self): + loader = Loader() + self.assertRaises(TypeError, loader.to_unicode, u"abc") + + decode_errors = defaults.DECODE_ERRORS + string_encoding = defaults.STRING_ENCODING + + nonascii = 'abcdé' + + try: + defaults.DECODE_ERRORS = 'strict' + defaults.STRING_ENCODING = 'ascii' + loader = Loader() + self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii) + + defaults.DECODE_ERRORS = 'ignore' + loader = Loader() + self.assertString(loader.to_unicode(nonascii), u'abcd') + + defaults.STRING_ENCODING = 'utf-8' + loader = Loader() + self.assertString(loader.to_unicode(nonascii), u'abcdé') + + finally: + defaults.DECODE_ERRORS = decode_errors + defaults.STRING_ENCODING = string_encoding + + def _get_path(self, filename): + return os.path.join(DATA_DIR, filename) + + def test_unicode__basic__input_str(self): + """ + Test unicode(): default arguments with str input. + + """ + reader = Loader() + actual = reader.unicode("foo") + + self.assertString(actual, u"foo") + + def test_unicode__basic__input_unicode(self): + """ + Test unicode(): default arguments with unicode input. + + """ + reader = Loader() + actual = reader.unicode(u"foo") + + self.assertString(actual, u"foo") + + def test_unicode__basic__input_unicode_subclass(self): + """ + Test unicode(): default arguments with unicode-subclass input. + + """ + class UnicodeSubclass(unicode): + pass + + s = UnicodeSubclass(u"foo") + + reader = Loader() + actual = reader.unicode(s) + + self.assertString(actual, u"foo") + + def test_unicode__to_unicode__attribute(self): + """ + Test unicode(): encoding attribute. + + """ + reader = Loader() + + non_ascii = u'abcdé'.encode('utf-8') + + self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii) + + def to_unicode(s, encoding=None): + if encoding is None: + encoding = 'utf-8' + return unicode(s, encoding) + + reader.to_unicode = to_unicode + self.assertString(reader.unicode(non_ascii), u"abcdé") + + def test_unicode__encoding_argument(self): + """ + Test unicode(): encoding argument. + + """ + reader = Loader() + + non_ascii = u'abcdé'.encode('utf-8') + + self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii) + + actual = reader.unicode(non_ascii, encoding='utf-8') + self.assertString(actual, u'abcdé') + + # TODO: check the read() unit tests. + def test_read(self): + """ + Test read(). + + """ + reader = Loader() + path = self._get_path('ascii.mustache') + actual = reader.read(path) + self.assertString(actual, u'ascii: abc') + + def test_read__file_encoding__attribute(self): + """ + Test read(): file_encoding attribute respected. + + """ + loader = Loader() + path = self._get_path('non_ascii.mustache') + + self.assertRaises(UnicodeDecodeError, loader.read, path) + + loader.file_encoding = 'utf-8' + actual = loader.read(path) + self.assertString(actual, u'non-ascii: é') + + def test_read__encoding__argument(self): + """ + Test read(): encoding argument respected. + + """ + reader = Loader() + path = self._get_path('non_ascii.mustache') + + self.assertRaises(UnicodeDecodeError, reader.read, path) + + actual = reader.read(path, encoding='utf-8') + self.assertString(actual, u'non-ascii: é') + + def test_reader__to_unicode__attribute(self): + """ + Test read(): to_unicode attribute respected. + + """ + reader = Loader() + path = self._get_path('non_ascii.mustache') + + self.assertRaises(UnicodeDecodeError, reader.read, path) + + #reader.decode_errors = 'ignore' + #actual = reader.read(path) + #self.assertString(actual, u'non-ascii: ') + diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py new file mode 100644 index 0000000..94a55ad --- /dev/null +++ b/pystache/tests/test_locator.py @@ -0,0 +1,150 @@ +# encoding: utf-8 + +""" +Contains locator.py unit tests. + +""" + +from datetime import datetime +import os +import sys +import unittest + +# TODO: remove this alias. +from pystache.loader import Loader as Reader +from pystache.locator import Locator + +from tests.common import DATA_DIR +from data.views import SayHello + + +class LocatorTests(unittest.TestCase): + + def _locator(self): + return Locator(search_dirs=DATA_DIR) + + def test_init__extension(self): + # Test the default value. + locator = Locator() + self.assertEquals(locator.template_extension, 'mustache') + + locator = Locator(extension='txt') + self.assertEquals(locator.template_extension, 'txt') + + locator = Locator(extension=False) + self.assertTrue(locator.template_extension is False) + + def test_get_object_directory(self): + locator = Locator() + + obj = SayHello() + actual = locator.get_object_directory(obj) + + self.assertEquals(actual, os.path.abspath(DATA_DIR)) + + def test_get_object_directory__not_hasattr_module(self): + locator = Locator() + + obj = datetime(2000, 1, 1) + self.assertFalse(hasattr(obj, '__module__')) + self.assertEquals(locator.get_object_directory(obj), None) + + self.assertFalse(hasattr(None, '__module__')) + self.assertEquals(locator.get_object_directory(None), None) + + def test_make_file_name(self): + locator = Locator() + + locator.template_extension = 'bar' + self.assertEquals(locator.make_file_name('foo'), 'foo.bar') + + locator.template_extension = False + self.assertEquals(locator.make_file_name('foo'), 'foo') + + locator.template_extension = '' + self.assertEquals(locator.make_file_name('foo'), 'foo.') + + def test_make_file_name__template_extension_argument(self): + locator = Locator() + + self.assertEquals(locator.make_file_name('foo', template_extension='bar'), 'foo.bar') + + def test_find_name(self): + locator = Locator() + path = locator.find_name(search_dirs=['examples'], template_name='simple') + + self.assertEquals(os.path.basename(path), 'simple.mustache') + + def test_find_name__using_list_of_paths(self): + locator = Locator() + path = locator.find_name(search_dirs=['doesnt_exist', 'examples'], template_name='simple') + + self.assertTrue(path) + + def test_find_name__precedence(self): + """ + Test the order in which find_name() searches directories. + + """ + locator = Locator() + + dir1 = DATA_DIR + dir2 = os.path.join(DATA_DIR, 'locator') + + self.assertTrue(locator.find_name(search_dirs=[dir1], template_name='duplicate')) + self.assertTrue(locator.find_name(search_dirs=[dir2], template_name='duplicate')) + + path = locator.find_name(search_dirs=[dir2, dir1], template_name='duplicate') + dirpath = os.path.dirname(path) + dirname = os.path.split(dirpath)[-1] + + self.assertEquals(dirname, 'locator') + + def test_find_name__non_existent_template_fails(self): + locator = Locator() + + self.assertRaises(IOError, locator.find_name, search_dirs=[], template_name='doesnt_exist') + + def test_find_object(self): + locator = Locator() + + obj = SayHello() + + actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache') + expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) + + self.assertEquals(actual, expected) + + def test_find_object__none_file_name(self): + locator = Locator() + + obj = SayHello() + + actual = locator.find_object(search_dirs=[], obj=obj) + expected = os.path.abspath(os.path.join(DATA_DIR, 'say_hello.mustache')) + + self.assertEquals(actual, expected) + + def test_find_object__none_object_directory(self): + locator = Locator() + + obj = None + self.assertEquals(None, locator.get_object_directory(obj)) + + actual = locator.find_object(search_dirs=[DATA_DIR], obj=obj, file_name='say_hello.mustache') + expected = os.path.join(DATA_DIR, 'say_hello.mustache') + + self.assertEquals(actual, expected) + + def test_make_template_name(self): + """ + Test make_template_name(). + + """ + locator = Locator() + + class FooBar(object): + pass + foo = FooBar() + + self.assertEquals(locator.make_template_name(foo), 'foo_bar') diff --git a/pystache/tests/test_pystache.py b/pystache/tests/test_pystache.py new file mode 100644 index 0000000..f9857cd --- /dev/null +++ b/pystache/tests/test_pystache.py @@ -0,0 +1,116 @@ +# encoding: utf-8 + +import unittest +import pystache +from pystache import renderer + + +class PystacheTests(unittest.TestCase): + + def _assert_rendered(self, expected, template, context): + actual = pystache.render(template, context) + self.assertEquals(actual, expected) + + def test_basic(self): + ret = pystache.render("Hi {{thing}}!", { 'thing': 'world' }) + self.assertEquals(ret, "Hi world!") + + def test_kwargs(self): + ret = pystache.render("Hi {{thing}}!", thing='world') + self.assertEquals(ret, "Hi world!") + + def test_less_basic(self): + template = "It's a nice day for {{beverage}}, right {{person}}?" + context = { 'beverage': 'soda', 'person': 'Bob' } + self._assert_rendered("It's a nice day for soda, right Bob?", template, context) + + def test_even_less_basic(self): + template = "I think {{name}} wants a {{thing}}, right {{name}}?" + context = { 'name': 'Jon', 'thing': 'racecar' } + self._assert_rendered("I think Jon wants a racecar, right Jon?", template, context) + + def test_ignores_misses(self): + template = "I think {{name}} wants a {{thing}}, right {{name}}?" + context = { 'name': 'Jon' } + self._assert_rendered("I think Jon wants a , right Jon?", template, context) + + def test_render_zero(self): + template = 'My value is {{value}}.' + context = { 'value': 0 } + self._assert_rendered('My value is 0.', template, context) + + def test_comments(self): + template = "What {{! the }} what?" + actual = pystache.render(template) + self.assertEquals("What what?", actual) + + def test_false_sections_are_hidden(self): + template = "Ready {{#set}}set {{/set}}go!" + context = { 'set': False } + self._assert_rendered("Ready go!", template, context) + + def test_true_sections_are_shown(self): + template = "Ready {{#set}}set{{/set}} go!" + context = { 'set': True } + self._assert_rendered("Ready set go!", template, context) + + non_strings_expected = """(123 & ['something'])(chris & 0.9)""" + + def test_non_strings(self): + template = "{{#stats}}({{key}} & {{value}}){{/stats}}" + stats = [] + stats.append({'key': 123, 'value': ['something']}) + stats.append({'key': u"chris", 'value': 0.900}) + context = { 'stats': stats } + self._assert_rendered(self.non_strings_expected, template, context) + + def test_unicode(self): + template = 'Name: {{name}}; Age: {{age}}' + context = {'name': u'Henri Poincaré', 'age': 156 } + self._assert_rendered(u'Name: Henri Poincaré; Age: 156', template, context) + + def test_sections(self): + template = """""" + + context = { 'users': [ {'name': 'Chris'}, {'name': 'Tom'}, {'name': 'PJ'} ] } + expected = """""" + self._assert_rendered(expected, template, context) + + def test_implicit_iterator(self): + template = """""" + context = { 'users': [ 'Chris', 'Tom','PJ' ] } + expected = """""" + self._assert_rendered(expected, template, context) + + # The spec says that sections should not alter surrounding whitespace. + def test_surrounding_whitepace_not_altered(self): + template = "first{{#spacing}} second {{/spacing}}third" + context = {"spacing": True} + self._assert_rendered("first second third", template, context) + + def test__section__non_false_value(self): + """ + Test when a section value is a (non-list) "non-false value". + + From mustache(5): + + When the value [of a section key] is non-false but not a list, it + will be used as the context for a single rendering of the block. + + """ + template = """{{#person}}Hi {{name}}{{/person}}""" + context = {"person": {"name": "Jon"}} + self._assert_rendered("Hi Jon", template, context) + + def test_later_list_section_with_escapable_character(self): + """ + This is a simple test case intended to cover issue #53. + + The test case failed with markupsafe enabled, as follows: + + AssertionError: Markup(u'foo <') != 'foo <' + + """ + template = """{{#s1}}foo{{/s1}} {{#s2}}<{{/s2}}""" + context = {'s1': True, 's2': [True]} + self._assert_rendered("foo <", template, context) diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py new file mode 100644 index 0000000..6c2831a --- /dev/null +++ b/pystache/tests/test_renderengine.py @@ -0,0 +1,455 @@ +# coding: utf-8 + +""" +Unit tests of renderengine.py. + +""" + +import cgi +import unittest + +from pystache.context import Context +from pystache.parser import ParsingError +from pystache.renderengine import RenderEngine +from tests.common import AssertStringMixin + + +class RenderEngineTestCase(unittest.TestCase): + + """Test the RenderEngine class.""" + + def test_init(self): + """ + Test that __init__() stores all of the arguments correctly. + + """ + # In real-life, these arguments would be functions + engine = RenderEngine(load_partial="foo", literal="literal", escape="escape") + + self.assertEquals(engine.escape, "escape") + self.assertEquals(engine.literal, "literal") + self.assertEquals(engine.load_partial, "foo") + + +class RenderTests(unittest.TestCase, AssertStringMixin): + + """ + Tests RenderEngine.render(). + + Explicit spec-test-like tests best go in this class since the + RenderEngine class contains all parsing logic. This way, the unit tests + will be more focused and fail "closer to the code". + + """ + + def _engine(self): + """ + Create and return a default RenderEngine for testing. + + """ + escape = lambda s: unicode(cgi.escape(s)) + engine = RenderEngine(literal=unicode, escape=escape, load_partial=None) + return engine + + def _assert_render(self, expected, template, *context, **kwargs): + """ + Test rendering the given template using the given context. + + """ + partials = kwargs.get('partials') + engine = kwargs.get('engine', self._engine()) + + if partials is not None: + engine.load_partial = lambda key: unicode(partials[key]) + + context = Context(*context) + + actual = engine.render(template, context) + + self.assertString(actual=actual, expected=expected) + + def test_render(self): + self._assert_render(u'Hi Mom', 'Hi {{person}}', {'person': 'Mom'}) + + def test__load_partial(self): + """ + Test that render() uses the load_template attribute. + + """ + engine = self._engine() + partials = {'partial': u"{{person}}"} + engine.load_partial = lambda key: partials[key] + + self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine) + + def test__literal(self): + """ + Test that render() uses the literal attribute. + + """ + engine = self._engine() + engine.literal = lambda s: s.upper() + + self._assert_render(u'BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine) + + def test_literal__sigil(self): + template = "

{{& thing}}

" + context = {'thing': 'Bear > Giraffe'} + + expected = u"

Bear > Giraffe

" + + self._assert_render(expected, template, context) + + def test__escape(self): + """ + Test that render() uses the escape attribute. + + """ + engine = self._engine() + engine.escape = lambda s: "**" + s + + self._assert_render(u'**bar', '{{foo}}', {'foo': 'bar'}, engine=engine) + + def test__escape_does_not_call_literal(self): + """ + Test that render() does not call literal before or after calling escape. + + """ + engine = self._engine() + engine.literal = lambda s: s.upper() # a test version + engine.escape = lambda s: "**" + s + + template = 'literal: {{{foo}}} escaped: {{foo}}' + context = {'foo': 'bar'} + + self._assert_render(u'literal: BAR escaped: **bar', template, context, engine=engine) + + def test__escape_preserves_unicode_subclasses(self): + """ + Test that render() preserves unicode subclasses when passing to escape. + + This is useful, for example, if one wants to respect whether a + variable value is markupsafe.Markup when escaping. + + """ + class MyUnicode(unicode): + pass + + def escape(s): + if type(s) is MyUnicode: + return "**" + s + else: + return s + "**" + + engine = self._engine() + engine.escape = escape + + template = '{{foo1}} {{foo2}}' + context = {'foo1': MyUnicode('bar'), 'foo2': 'bar'} + + self._assert_render(u'**bar bar**', template, context, engine=engine) + + def test__non_basestring__literal_and_escaped(self): + """ + Test a context value that is not a basestring instance. + + """ + # We use include upper() to make sure we are actually using + # our custom function in the tests + to_unicode = lambda s: unicode(s, encoding='ascii').upper() + engine = self._engine() + engine.escape = to_unicode + engine.literal = to_unicode + + self.assertRaises(TypeError, engine.literal, 100) + + template = '{{text}} {{int}} {{{int}}}' + context = {'int': 100, 'text': 'foo'} + + self._assert_render(u'FOO 100 100', template, context, engine=engine) + + def test_tag__output_not_interpolated(self): + """ + Context values should not be treated as templates (issue #44). + + """ + template = '{{template}}: {{planet}}' + context = {'template': '{{planet}}', 'planet': 'Earth'} + self._assert_render(u'{{planet}}: Earth', template, context) + + def test_tag__output_not_interpolated__section(self): + """ + Context values should not be treated as templates (issue #44). + + """ + template = '{{test}}' + context = {'test': '{{#hello}}'} + self._assert_render(u'{{#hello}}', template, context) + + def test_interpolation__built_in_type__string(self): + """ + Check tag interpolation with a string on the top of the context stack. + + """ + item = 'abc' + # item.upper() == 'ABC' + template = '{{#section}}{{upper}}{{/section}}' + context = {'section': item, 'upper': 'XYZ'} + self._assert_render(u'XYZ', template, context) + + def test_interpolation__built_in_type__integer(self): + """ + Check tag interpolation with an integer on the top of the context stack. + + """ + item = 10 + # item.real == 10 + template = '{{#section}}{{real}}{{/section}}' + context = {'section': item, 'real': 1000} + self._assert_render(u'1000', template, context) + + def test_interpolation__built_in_type__list(self): + """ + Check tag interpolation with a list on the top of the context stack. + + """ + item = [[1, 2, 3]] + # item[0].pop() == 3 + template = '{{#section}}{{pop}}{{/section}}' + context = {'section': item, 'pop': 7} + self._assert_render(u'7', template, context) + + def test_implicit_iterator__literal(self): + """ + Test an implicit iterator in a literal tag. + + """ + template = """{{#test}}{{{.}}}{{/test}}""" + context = {'test': ['<', '>']} + + self._assert_render(u'<>', template, context) + + def test_implicit_iterator__escaped(self): + """ + Test an implicit iterator in a normal tag. + + """ + template = """{{#test}}{{.}}{{/test}}""" + context = {'test': ['<', '>']} + + self._assert_render(u'<>', template, context) + + def test_literal__in_section(self): + """ + Check that literals work in sections. + + """ + template = '{{#test}}1 {{{less_than}}} 2{{/test}}' + context = {'test': {'less_than': '<'}} + + self._assert_render(u'1 < 2', template, context) + + def test_literal__in_partial(self): + """ + Check that literals work in partials. + + """ + template = '{{>partial}}' + partials = {'partial': '1 {{{less_than}}} 2'} + context = {'less_than': '<'} + + self._assert_render(u'1 < 2', template, context, partials=partials) + + def test_partial(self): + partials = {'partial': "{{person}}"} + self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials) + + def test_partial__context_values(self): + """ + Test that escape and literal work on context values in partials. + + """ + engine = self._engine() + + template = '{{>partial}}' + partials = {'partial': 'unescaped: {{{foo}}} escaped: {{foo}}'} + context = {'foo': '<'} + + self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine, partials=partials) + + ## Test cases related specifically to sections. + + def test_section__end_tag_with_no_start_tag(self): + """ + Check what happens if there is an end tag with no start tag. + + """ + template = '{{/section}}' + try: + self._assert_render(None, template) + except ParsingError, err: + self.assertEquals(str(err), "Section end tag mismatch: u'section' != None") + + def test_section__end_tag_mismatch(self): + """ + Check what happens if the end tag doesn't match. + + """ + template = '{{#section_start}}{{/section_end}}' + try: + self._assert_render(None, template) + except ParsingError, err: + self.assertEquals(str(err), "Section end tag mismatch: u'section_end' != u'section_start'") + + def test_section__context_values(self): + """ + Test that escape and literal work on context values in sections. + + """ + engine = self._engine() + + template = '{{#test}}unescaped: {{{foo}}} escaped: {{foo}}{{/test}}' + context = {'test': {'foo': '<'}} + + self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine) + + def test_section__context_precedence(self): + """ + Check that items higher in the context stack take precedence. + + """ + template = '{{entree}} : {{#vegetarian}}{{entree}}{{/vegetarian}}' + context = {'entree': 'chicken', 'vegetarian': {'entree': 'beans and rice'}} + self._assert_render(u'chicken : beans and rice', template, context) + + def test_section__list_referencing_outer_context(self): + """ + Check that list items can access the parent context. + + For sections whose value is a list, check that items in the list + have access to the values inherited from the parent context + when rendering. + + """ + context = { + "greeting": "Hi", + "list": [{"name": "Al"}, {"name": "Bob"}], + } + + template = "{{#list}}{{greeting}} {{name}}, {{/list}}" + + self._assert_render(u"Hi Al, Hi Bob, ", template, context) + + def test_section__output_not_interpolated(self): + """ + Check that rendered section output is not interpolated. + + """ + template = '{{#section}}{{template}}{{/section}}: {{planet}}' + context = {'section': True, 'template': '{{planet}}', 'planet': 'Earth'} + self._assert_render(u'{{planet}}: Earth', template, context) + + def test_section__nested_truthy(self): + """ + Check that "nested truthy" sections get rendered. + + Test case for issue #24: https://github.com/defunkt/pystache/issues/24 + + This test is copied from the spec. We explicitly include it to + prevent regressions for those who don't pull down the spec tests. + + """ + template = '| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |' + context = {'bool': True} + self._assert_render(u'| A B C D E |', template, context) + + def test_section__nested_with_same_keys(self): + """ + Check a doubly-nested section with the same context key. + + Test case for issue #36: https://github.com/defunkt/pystache/issues/36 + + """ + # Start with an easier, working case. + template = '{{#x}}{{#z}}{{y}}{{/z}}{{/x}}' + context = {'x': {'z': {'y': 1}}} + self._assert_render(u'1', template, context) + + template = '{{#x}}{{#x}}{{y}}{{/x}}{{/x}}' + context = {'x': {'x': {'y': 1}}} + self._assert_render(u'1', template, context) + + def test_section__lambda(self): + template = '{{#test}}Mom{{/test}}' + context = {'test': (lambda text: 'Hi %s' % text)} + self._assert_render(u'Hi Mom', template, context) + + def test_section__iterable(self): + """ + Check that objects supporting iteration (aside from dicts) behave like lists. + + """ + template = '{{#iterable}}{{.}}{{/iterable}}' + + context = {'iterable': (i for i in range(3))} # type 'generator' + self._assert_render(u'012', template, context) + + context = {'iterable': xrange(4)} # type 'xrange' + self._assert_render(u'0123', template, context) + + d = {'foo': 0, 'bar': 0} + # We don't know what order of keys we'll be given, but from the + # Python documentation: + # "If items(), keys(), values(), iteritems(), iterkeys(), and + # itervalues() are called with no intervening modifications to + # the dictionary, the lists will directly correspond." + expected = u''.join(d.keys()) + context = {'iterable': d.iterkeys()} # type 'dictionary-keyiterator' + self._assert_render(expected, template, context) + + def test_section__lambda__tag_in_output(self): + """ + Check that callable output is treated as a template string (issue #46). + + The spec says-- + + When used as the data value for a Section tag, the lambda MUST + be treatable as an arity 1 function, and invoked as such (passing + a String containing the unprocessed section contents). The + returned value MUST be rendered against the current delimiters, + then interpolated in place of the section. + + """ + template = '{{#test}}Hi {{person}}{{/test}}' + context = {'person': 'Mom', 'test': (lambda text: text + " :)")} + self._assert_render(u'Hi Mom :)', template, context) + + def test_comment__multiline(self): + """ + Check that multiline comments are permitted. + + """ + self._assert_render(u'foobar', 'foo{{! baz }}bar') + self._assert_render(u'foobar', 'foo{{! \nbaz }}bar') + + def test_custom_delimiters__sections(self): + """ + Check that custom delimiters can be used to start a section. + + Test case for issue #20: https://github.com/defunkt/pystache/issues/20 + + """ + template = '{{=[[ ]]=}}[[#foo]]bar[[/foo]]' + context = {'foo': True} + self._assert_render(u'bar', template, context) + + def test_custom_delimiters__not_retroactive(self): + """ + Check that changing custom delimiters back is not "retroactive." + + Test case for issue #35: https://github.com/defunkt/pystache/issues/35 + + """ + expected = u' {{foo}} ' + self._assert_render(expected, '{{=$ $=}} {{foo}} ') + self._assert_render(expected, '{{=$ $=}} {{foo}} $={{ }}=$') # was yielding u' '. diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py new file mode 100644 index 0000000..a69d11a --- /dev/null +++ b/pystache/tests/test_renderer.py @@ -0,0 +1,556 @@ +# coding: utf-8 + +""" +Unit tests of template.py. + +""" + +import codecs +import os +import sys +import unittest + +from examples.simple import Simple +from pystache import Renderer +from pystache import TemplateSpec +from pystache.loader import Loader + +from tests.common import get_data_path +from tests.common import AssertStringMixin +from tests.data.views import SayHello + + +class RendererInitTestCase(unittest.TestCase): + + """ + Tests the Renderer.__init__() method. + + """ + + def test_partials__default(self): + """ + Test the default value. + + """ + renderer = Renderer() + self.assertTrue(renderer.partials is None) + + def test_partials(self): + """ + Test that the attribute is set correctly. + + """ + renderer = Renderer(partials={'foo': 'bar'}) + self.assertEquals(renderer.partials, {'foo': 'bar'}) + + def test_escape__default(self): + escape = Renderer().escape + + self.assertEquals(escape(">"), ">") + self.assertEquals(escape('"'), """) + # Single quotes are not escaped. + self.assertEquals(escape("'"), "'") + + def test_escape(self): + escape = lambda s: "**" + s + renderer = Renderer(escape=escape) + self.assertEquals(renderer.escape("bar"), "**bar") + + def test_decode_errors__default(self): + """ + Check the default value. + + """ + renderer = Renderer() + self.assertEquals(renderer.decode_errors, 'strict') + + def test_decode_errors(self): + """ + Check that the constructor sets the attribute correctly. + + """ + renderer = Renderer(decode_errors="foo") + self.assertEquals(renderer.decode_errors, "foo") + + def test_file_encoding__default(self): + """ + Check the file_encoding default. + + """ + renderer = Renderer() + self.assertEquals(renderer.file_encoding, renderer.string_encoding) + + def test_file_encoding(self): + """ + Check that the file_encoding attribute is set correctly. + + """ + renderer = Renderer(file_encoding='foo') + self.assertEquals(renderer.file_encoding, 'foo') + + def test_file_extension__default(self): + """ + Check the file_extension default. + + """ + renderer = Renderer() + self.assertEquals(renderer.file_extension, 'mustache') + + def test_file_extension(self): + """ + Check that the file_encoding attribute is set correctly. + + """ + renderer = Renderer(file_extension='foo') + self.assertEquals(renderer.file_extension, 'foo') + + def test_search_dirs__default(self): + """ + Check the search_dirs default. + + """ + renderer = Renderer() + self.assertEquals(renderer.search_dirs, [os.curdir]) + + def test_search_dirs__string(self): + """ + Check that the search_dirs attribute is set correctly when a string. + + """ + renderer = Renderer(search_dirs='foo') + self.assertEquals(renderer.search_dirs, ['foo']) + + def test_search_dirs__list(self): + """ + Check that the search_dirs attribute is set correctly when a list. + + """ + renderer = Renderer(search_dirs=['foo']) + self.assertEquals(renderer.search_dirs, ['foo']) + + def test_string_encoding__default(self): + """ + Check the default value. + + """ + renderer = Renderer() + self.assertEquals(renderer.string_encoding, sys.getdefaultencoding()) + + def test_string_encoding(self): + """ + Check that the constructor sets the attribute correctly. + + """ + renderer = Renderer(string_encoding="foo") + self.assertEquals(renderer.string_encoding, "foo") + + +class RendererTests(unittest.TestCase, AssertStringMixin): + + """Test the Renderer class.""" + + def _renderer(self): + return Renderer() + + ## Test Renderer.unicode(). + + def test_unicode__string_encoding(self): + """ + Test that the string_encoding attribute is respected. + + """ + renderer = Renderer() + s = "é" + + renderer.string_encoding = "ascii" + self.assertRaises(UnicodeDecodeError, renderer.unicode, s) + + renderer.string_encoding = "utf-8" + self.assertEquals(renderer.unicode(s), u"é") + + def test_unicode__decode_errors(self): + """ + Test that the decode_errors attribute is respected. + + """ + renderer = Renderer() + renderer.string_encoding = "ascii" + s = "déf" + + renderer.decode_errors = "ignore" + self.assertEquals(renderer.unicode(s), "df") + + renderer.decode_errors = "replace" + # U+FFFD is the official Unicode replacement character. + self.assertEquals(renderer.unicode(s), u'd\ufffd\ufffdf') + + ## Test the _make_loader() method. + + def test__make_loader__return_type(self): + """ + Test that _make_loader() returns a Loader. + + """ + renderer = Renderer() + loader = renderer._make_loader() + + self.assertEquals(type(loader), Loader) + + def test__make_loader__attributes(self): + """ + Test that _make_loader() sets all attributes correctly.. + + """ + unicode_ = lambda x: x + + renderer = Renderer() + renderer.file_encoding = 'enc' + renderer.file_extension = 'ext' + renderer.unicode = unicode_ + + loader = renderer._make_loader() + + self.assertEquals(loader.extension, 'ext') + self.assertEquals(loader.file_encoding, 'enc') + self.assertEquals(loader.to_unicode, unicode_) + + ## Test the render() method. + + def test_render__return_type(self): + """ + Check that render() returns a string of type unicode. + + """ + renderer = Renderer() + rendered = renderer.render('foo') + self.assertEquals(type(rendered), unicode) + + def test_render__unicode(self): + renderer = Renderer() + actual = renderer.render(u'foo') + self.assertEquals(actual, u'foo') + + def test_render__str(self): + renderer = Renderer() + actual = renderer.render('foo') + self.assertEquals(actual, 'foo') + + def test_render__non_ascii_character(self): + renderer = Renderer() + actual = renderer.render(u'Poincaré') + self.assertEquals(actual, u'Poincaré') + + def test_render__context(self): + """ + Test render(): passing a context. + + """ + renderer = Renderer() + self.assertEquals(renderer.render('Hi {{person}}', {'person': 'Mom'}), 'Hi Mom') + + def test_render__context_and_kwargs(self): + """ + Test render(): passing a context and **kwargs. + + """ + renderer = Renderer() + template = 'Hi {{person1}} and {{person2}}' + self.assertEquals(renderer.render(template, {'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad') + + def test_render__kwargs_and_no_context(self): + """ + Test render(): passing **kwargs and no context. + + """ + renderer = Renderer() + self.assertEquals(renderer.render('Hi {{person}}', person='Mom'), 'Hi Mom') + + def test_render__context_and_kwargs__precedence(self): + """ + Test render(): **kwargs takes precedence over context. + + """ + renderer = Renderer() + self.assertEquals(renderer.render('Hi {{person}}', {'person': 'Mom'}, person='Dad'), 'Hi Dad') + + def test_render__kwargs_does_not_modify_context(self): + """ + Test render(): passing **kwargs does not modify the passed context. + + """ + context = {} + renderer = Renderer() + renderer.render('Hi {{person}}', context=context, foo="bar") + self.assertEquals(context, {}) + + def test_render__nonascii_template(self): + """ + Test passing a non-unicode template with non-ascii characters. + + """ + renderer = Renderer() + template = "déf" + + # Check that decode_errors and string_encoding are both respected. + renderer.decode_errors = 'ignore' + renderer.string_encoding = 'ascii' + self.assertEquals(renderer.render(template), "df") + + renderer.string_encoding = 'utf_8' + self.assertEquals(renderer.render(template), u"déf") + + def test_make_load_partial(self): + """ + Test the _make_load_partial() method. + + """ + renderer = Renderer() + renderer.partials = {'foo': 'bar'} + load_partial = renderer._make_load_partial() + + actual = load_partial('foo') + self.assertEquals(actual, 'bar') + self.assertEquals(type(actual), unicode, "RenderEngine requires that " + "load_partial return unicode strings.") + + def test_make_load_partial__unicode(self): + """ + Test _make_load_partial(): that load_partial doesn't "double-decode" Unicode. + + """ + renderer = Renderer() + + renderer.partials = {'partial': 'foo'} + load_partial = renderer._make_load_partial() + self.assertEquals(load_partial("partial"), "foo") + + # Now with a value that is already unicode. + renderer.partials = {'partial': u'foo'} + load_partial = renderer._make_load_partial() + # If the next line failed, we would get the following error: + # TypeError: decoding Unicode is not supported + self.assertEquals(load_partial("partial"), "foo") + + def test_render_path(self): + """ + Test the render_path() method. + + """ + renderer = Renderer() + path = get_data_path('say_hello.mustache') + actual = renderer.render_path(path, to='foo') + self.assertEquals(actual, "Hello, foo") + + def test_render__object(self): + """ + Test rendering an object instance. + + """ + renderer = Renderer() + + say_hello = SayHello() + actual = renderer.render(say_hello) + self.assertEquals('Hello, World', actual) + + actual = renderer.render(say_hello, to='Mars') + self.assertEquals('Hello, Mars', actual) + + def test_render__template_spec(self): + """ + Test rendering a TemplateSpec instance. + + """ + renderer = Renderer() + + class Spec(TemplateSpec): + template = "hello, {{to}}" + to = 'world' + + spec = Spec() + actual = renderer.render(spec) + self.assertString(actual, u'hello, world') + + def test_render__view(self): + """ + Test rendering a View instance. + + """ + renderer = Renderer() + + view = Simple() + actual = renderer.render(view) + self.assertEquals('Hi pizza!', actual) + + +# By testing that Renderer.render() constructs the right RenderEngine, +# we no longer need to exercise all rendering code paths through +# the Renderer. It suffices to test rendering paths through the +# RenderEngine for the same amount of code coverage. +class Renderer_MakeRenderEngineTests(unittest.TestCase): + + """ + Check the RenderEngine returned by Renderer._make_render_engine(). + + """ + + ## Test the engine's load_partial attribute. + + def test__load_partial__returns_unicode(self): + """ + Check that load_partial returns unicode (and not a subclass). + + """ + class MyUnicode(unicode): + pass + + renderer = Renderer() + renderer.string_encoding = 'ascii' + renderer.partials = {'str': 'foo', 'subclass': MyUnicode('abc')} + + engine = renderer._make_render_engine() + + actual = engine.load_partial('str') + self.assertEquals(actual, "foo") + self.assertEquals(type(actual), unicode) + + # Check that unicode subclasses are not preserved. + actual = engine.load_partial('subclass') + self.assertEquals(actual, "abc") + self.assertEquals(type(actual), unicode) + + def test__load_partial__not_found(self): + """ + Check that load_partial provides a nice message when a template is not found. + + """ + renderer = Renderer() + renderer.partials = {} + + engine = renderer._make_render_engine() + load_partial = engine.load_partial + + try: + load_partial("foo") + raise Exception("Shouldn't get here") + except Exception, err: + self.assertEquals(str(err), "Partial not found with name: 'foo'") + + ## Test the engine's literal attribute. + + def test__literal__uses_renderer_unicode(self): + """ + Test that literal uses the renderer's unicode function. + + """ + renderer = Renderer() + renderer.unicode = lambda s: s.upper() + + engine = renderer._make_render_engine() + literal = engine.literal + + self.assertEquals(literal("foo"), "FOO") + + def test__literal__handles_unicode(self): + """ + Test that literal doesn't try to "double decode" unicode. + + """ + renderer = Renderer() + renderer.string_encoding = 'ascii' + + engine = renderer._make_render_engine() + literal = engine.literal + + self.assertEquals(literal(u"foo"), "foo") + + def test__literal__returns_unicode(self): + """ + Test that literal returns unicode (and not a subclass). + + """ + renderer = Renderer() + renderer.string_encoding = 'ascii' + + engine = renderer._make_render_engine() + literal = engine.literal + + self.assertEquals(type(literal("foo")), unicode) + + class MyUnicode(unicode): + pass + + s = MyUnicode("abc") + + self.assertEquals(type(s), MyUnicode) + self.assertTrue(isinstance(s, unicode)) + self.assertEquals(type(literal(s)), unicode) + + ## Test the engine's escape attribute. + + def test__escape__uses_renderer_escape(self): + """ + Test that escape uses the renderer's escape function. + + """ + renderer = Renderer() + renderer.escape = lambda s: "**" + s + + engine = renderer._make_render_engine() + escape = engine.escape + + self.assertEquals(escape("foo"), "**foo") + + def test__escape__uses_renderer_unicode(self): + """ + Test that escape uses the renderer's unicode function. + + """ + renderer = Renderer() + renderer.unicode = lambda s: s.upper() + + engine = renderer._make_render_engine() + escape = engine.escape + + self.assertEquals(escape("foo"), "FOO") + + def test__escape__has_access_to_original_unicode_subclass(self): + """ + Test that escape receives strings with the unicode subclass intact. + + """ + renderer = Renderer() + renderer.escape = lambda s: type(s).__name__ + + engine = renderer._make_render_engine() + escape = engine.escape + + class MyUnicode(unicode): + pass + + self.assertEquals(escape("foo"), "unicode") + self.assertEquals(escape(u"foo"), "unicode") + self.assertEquals(escape(MyUnicode("foo")), "MyUnicode") + + def test__escape__returns_unicode(self): + """ + Test that literal returns unicode (and not a subclass). + + """ + renderer = Renderer() + renderer.string_encoding = 'ascii' + + engine = renderer._make_render_engine() + escape = engine.escape + + self.assertEquals(type(escape("foo")), unicode) + + # Check that literal doesn't preserve unicode subclasses. + class MyUnicode(unicode): + pass + + s = MyUnicode("abc") + + self.assertEquals(type(s), MyUnicode) + self.assertTrue(isinstance(s, unicode)) + self.assertEquals(type(escape(s)), unicode) + diff --git a/pystache/tests/test_simple.py b/pystache/tests/test_simple.py new file mode 100644 index 0000000..e19187f --- /dev/null +++ b/pystache/tests/test_simple.py @@ -0,0 +1,83 @@ +import unittest + +import pystache +from pystache import Renderer +from examples.nested_context import NestedContext +from examples.complex import Complex +from examples.lambdas import Lambdas +from examples.template_partial import TemplatePartial +from examples.simple import Simple + +from tests.common import EXAMPLES_DIR +from tests.common import AssertStringMixin + + +class TestSimple(unittest.TestCase, AssertStringMixin): + + def test_nested_context(self): + renderer = Renderer() + view = NestedContext(renderer) + view.template = '{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}' + + actual = renderer.render(view) + self.assertString(actual, u"one and foo and two") + + def test_looping_and_negation_context(self): + template = '{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}' + context = Complex() + + renderer = Renderer() + actual = renderer.render(template, context) + self.assertEquals(actual, "Colors: red Colors: green Colors: blue ") + + def test_empty_context(self): + template = '{{#empty_list}}Shouldnt see me {{/empty_list}}{{^empty_list}}Should see me{{/empty_list}}' + self.assertEquals(pystache.Renderer().render(template), "Should see me") + + def test_callables(self): + view = Lambdas() + view.template = '{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}' + + renderer = Renderer() + actual = renderer.render(view) + self.assertString(actual, u'bar != bar. oh, it does!') + + def test_rendering_partial(self): + renderer = Renderer(search_dirs=EXAMPLES_DIR) + + view = TemplatePartial(renderer=renderer) + view.template = '{{>inner_partial}}' + + actual = renderer.render(view) + self.assertString(actual, u'Again, Welcome!') + + view.template = '{{#looping}}{{>inner_partial}} {{/looping}}' + actual = renderer.render(view) + self.assertString(actual, u"Again, Welcome! Again, Welcome! Again, Welcome! ") + + def test_non_existent_value_renders_blank(self): + view = Simple() + template = '{{not_set}} {{blank}}' + self.assertEquals(pystache.Renderer().render(template), ' ') + + + def test_template_partial_extension(self): + """ + Side note: + + From the spec-- + + Partial tags SHOULD be treated as standalone when appropriate. + + In particular, this means that trailing newlines should be removed. + + """ + renderer = Renderer(search_dirs=EXAMPLES_DIR, file_extension='txt') + + view = TemplatePartial(renderer=renderer) + + actual = renderer.render(view) + self.assertString(actual, u"""Welcome +------- + +## Again, Welcome! ##""") diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py new file mode 100644 index 0000000..9e7759a --- /dev/null +++ b/pystache/tests/test_spec.py @@ -0,0 +1,130 @@ +# coding: utf-8 + +""" +Creates a unittest.TestCase for the tests defined in the mustache spec. + +""" + +# TODO: this module can be cleaned up somewhat. + +FILE_ENCODING = 'utf-8' # the encoding of the spec test files. + + +try: + # We use the JSON files rather than the YAML files because json libraries + # are available for Python 2.4. + import json +except: + # The module json is not available prior to Python 2.6, whereas simplejson is. + # Note that simplejson dropped support for Python 2.4 in simplejson v2.1.0, + # so Python 2.4 requires a simplejson install older than the most recent. + import simplejson as json + +import glob +import os.path +import unittest + +from pystache.renderer import Renderer +from tests.common import AssertStringMixin + + +root_path = os.path.join(os.path.dirname(__file__), '..', 'ext', 'spec', 'specs') +spec_paths = glob.glob(os.path.join(root_path, '*.json')) + + +# TODO: give this a name better than MustacheSpec. +class MustacheSpec(unittest.TestCase, AssertStringMixin): + pass + +def buildTest(testData, spec_filename): + + name = testData['name'] + description = testData['desc'] + + test_name = "%s (%s)" % (name, spec_filename) + + def test(self): + template = testData['template'] + partials = testData.has_key('partials') and testData['partials'] or {} + expected = testData['expected'] + data = testData['data'] + + # Convert code strings to functions. + # TODO: make this section of code easier to understand. + new_data = {} + for key, val in data.iteritems(): + if isinstance(val, dict) and val.get('__tag__') == 'code': + val = eval(val['python']) + new_data[key] = val + + renderer = Renderer(partials=partials) + actual = renderer.render(template, new_data) + + # We need to escape the strings that occur in our format string because + # they can contain % symbols, for example (in delimiters.yml)-- + # + # "template: '{{=<% %>=}}(<%text%>)'" + # + def escape(s): + return s.replace("%", "%%") + + subs = [description, template, json.__version__, str(json)] + subs = tuple([escape(sub) for sub in subs]) + # We include the json module version to help in troubleshooting + # json/simplejson issues. + message = """%s + + Template: \"""%s\""" + + %%s + + (using version %s of %s) + """ % subs + + self.assertString(actual, expected, format=message) + + # The name must begin with "test" for nosetests test discovery to work. + name = 'test: "%s"' % test_name + + # If we don't convert unicode to str, we get the following error: + # "TypeError: __name__ must be set to a string object" + test.__name__ = str(name) + + return test + +for spec_path in spec_paths: + + file_name = os.path.basename(spec_path) + + # We avoid use of the with keyword for Python 2.4 support. + f = open(spec_path, 'r') + try: + s = f.read() + finally: + f.close() + + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # + u = s.decode(FILE_ENCODING) + spec_data = json.loads(u) + + tests = spec_data['tests'] + + for test in tests: + test = buildTest(test, file_name) + setattr(MustacheSpec, test.__name__, test) + # Prevent this variable from being interpreted as another test. + del(test) + +if __name__ == '__main__': + unittest.main() diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py new file mode 100644 index 0000000..9599c37 --- /dev/null +++ b/pystache/tests/test_template_spec.py @@ -0,0 +1,388 @@ +# coding: utf-8 + +""" +Unit tests for template_spec.py. + +""" + +import os.path +import sys +import unittest + +import examples +from examples.simple import Simple +from examples.complex import Complex +from examples.lambdas import Lambdas +from examples.inverted import Inverted, InvertedLists +from pystache import Renderer +from pystache import TemplateSpec +from pystache.locator import Locator +from pystache.loader import Loader +from pystache.spec_loader import SpecLoader +from tests.common import DATA_DIR +from tests.common import EXAMPLES_DIR +from tests.common import AssertIsMixin +from tests.common import AssertStringMixin +from tests.data.views import SampleView +from tests.data.views import NonAscii + + +class Thing(object): + pass + + +class ViewTestCase(unittest.TestCase, AssertStringMixin): + + def test_template_rel_directory(self): + """ + Test that View.template_rel_directory is respected. + + """ + class Tagless(TemplateSpec): + pass + + view = Tagless() + renderer = Renderer() + + self.assertRaises(IOError, renderer.render, view) + + view.template_rel_directory = "../examples" + actual = renderer.render(view) + self.assertEquals(actual, "No tags...") + + def test_template_path_for_partials(self): + """ + Test that View.template_rel_path is respected for partials. + + """ + spec = TemplateSpec() + spec.template = "Partial: {{>tagless}}" + + renderer1 = Renderer() + renderer2 = Renderer(search_dirs=EXAMPLES_DIR) + + self.assertRaises(IOError, renderer1.render, spec) + + actual = renderer2.render(spec) + self.assertEquals(actual, "Partial: No tags...") + + def test_basic_method_calls(self): + renderer = Renderer() + actual = renderer.render(Simple()) + + self.assertString(actual, u"Hi pizza!") + + def test_non_callable_attributes(self): + view = Simple() + view.thing = 'Chris' + + renderer = Renderer() + actual = renderer.render(view) + self.assertEquals(actual, "Hi Chris!") + + def test_complex(self): + renderer = Renderer() + actual = renderer.render(Complex()) + self.assertString(actual, u"""\ +

Colors

+""") + + def test_higher_order_replace(self): + renderer = Renderer() + actual = renderer.render(Lambdas()) + self.assertEquals(actual, 'bar != bar. oh, it does!') + + def test_higher_order_rot13(self): + view = Lambdas() + view.template = '{{#rot13}}abcdefghijklm{{/rot13}}' + + renderer = Renderer() + actual = renderer.render(view) + self.assertString(actual, u'nopqrstuvwxyz') + + def test_higher_order_lambda(self): + view = Lambdas() + view.template = '{{#sort}}zyxwvutsrqponmlkjihgfedcba{{/sort}}' + + renderer = Renderer() + actual = renderer.render(view) + self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz') + + def test_partials_with_lambda(self): + view = Lambdas() + view.template = '{{>partial_with_lambda}}' + + renderer = Renderer(search_dirs=EXAMPLES_DIR) + actual = renderer.render(view) + self.assertEquals(actual, u'nopqrstuvwxyz') + + def test_hierarchical_partials_with_lambdas(self): + view = Lambdas() + view.template = '{{>partial_with_partial_and_lambda}}' + + renderer = Renderer(search_dirs=EXAMPLES_DIR) + actual = renderer.render(view) + self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz') + + def test_inverted(self): + renderer = Renderer() + actual = renderer.render(Inverted()) + self.assertString(actual, u"""one, two, three, empty list""") + + def test_accessing_properties_on_parent_object_from_child_objects(self): + parent = Thing() + parent.this = 'derp' + parent.children = [Thing()] + view = Simple() + view.template = "{{#parent}}{{#children}}{{this}}{{/children}}{{/parent}}" + + renderer = Renderer() + actual = renderer.render(view, {'parent': parent}) + + self.assertString(actual, u'derp') + + def test_inverted_lists(self): + renderer = Renderer() + actual = renderer.render(InvertedLists()) + self.assertString(actual, u"""one, two, three, empty list""") + + +class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): + + """ + Tests template_spec.SpecLoader. + + """ + + def test_init__defaults(self): + custom = SpecLoader() + + # Check the loader attribute. + loader = custom.loader + self.assertEquals(loader.extension, 'mustache') + self.assertEquals(loader.file_encoding, sys.getdefaultencoding()) + # TODO: finish testing the other Loader attributes. + to_unicode = loader.to_unicode + + def test_init__loader(self): + loader = Loader() + custom = SpecLoader(loader=loader) + + self.assertIs(custom.loader, loader) + + # TODO: rename to something like _assert_load(). + def _assert_template(self, loader, custom, expected): + self.assertString(loader.load(custom), expected) + + def test_load__template__type_str(self): + """ + Test the template attribute: str string. + + """ + custom = TemplateSpec() + custom.template = "abc" + + self._assert_template(SpecLoader(), custom, u"abc") + + def test_load__template__type_unicode(self): + """ + Test the template attribute: unicode string. + + """ + custom = TemplateSpec() + custom.template = u"abc" + + self._assert_template(SpecLoader(), custom, u"abc") + + def test_load__template__unicode_non_ascii(self): + """ + Test the template attribute: non-ascii unicode string. + + """ + custom = TemplateSpec() + custom.template = u"é" + + self._assert_template(SpecLoader(), custom, u"é") + + def test_load__template__with_template_encoding(self): + """ + Test the template attribute: with template encoding attribute. + + """ + custom = TemplateSpec() + custom.template = u'é'.encode('utf-8') + + self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') + + custom.template_encoding = 'utf-8' + self._assert_template(SpecLoader(), custom, u'é') + + # TODO: make this test complete. + def test_load__template__correct_loader(self): + """ + Test that reader.unicode() is called correctly. + + This test tests that the correct reader is called with the correct + arguments. This is a catch-all test to supplement the other + test cases. It tests SpecLoader.load() independent of reader.unicode() + being implemented correctly (and tested). + + """ + class MockLoader(Loader): + + def __init__(self): + self.s = None + self.encoding = None + + # Overrides the existing method. + def unicode(self, s, encoding=None): + self.s = s + self.encoding = encoding + return u"foo" + + loader = MockLoader() + custom_loader = SpecLoader() + custom_loader.loader = loader + + view = TemplateSpec() + view.template = "template-foo" + view.template_encoding = "encoding-foo" + + # Check that our unicode() above was called. + self._assert_template(custom_loader, view, u'foo') + self.assertEquals(loader.s, "template-foo") + self.assertEquals(loader.encoding, "encoding-foo") + + +# TODO: migrate these tests into the SpecLoaderTests class. +# TODO: rename the get_template() tests to test load(). +# TODO: condense, reorganize, and rename the tests so that it is +# clear whether we have full test coverage (e.g. organized by +# TemplateSpec attributes or something). +class TemplateSpecTests(unittest.TestCase): + + # TODO: rename this method to _make_loader(). + def _make_locator(self): + return SpecLoader() + + def _assert_template_location(self, view, expected): + locator = self._make_locator() + actual = locator._find_relative(view) + self.assertEquals(actual, expected) + + def test_find_relative(self): + """ + Test _find_relative(): default behavior (no attributes set). + + """ + view = SampleView() + self._assert_template_location(view, (None, 'sample_view.mustache')) + + def test_find_relative__template_rel_path__file_name_only(self): + """ + Test _find_relative(): template_rel_path attribute. + + """ + view = SampleView() + view.template_rel_path = 'template.txt' + self._assert_template_location(view, ('', 'template.txt')) + + def test_find_relative__template_rel_path__file_name_with_directory(self): + """ + Test _find_relative(): template_rel_path attribute. + + """ + view = SampleView() + view.template_rel_path = 'foo/bar/template.txt' + self._assert_template_location(view, ('foo/bar', 'template.txt')) + + def test_find_relative__template_rel_directory(self): + """ + Test _find_relative(): template_rel_directory attribute. + + """ + view = SampleView() + view.template_rel_directory = 'foo' + + self._assert_template_location(view, ('foo', 'sample_view.mustache')) + + def test_find_relative__template_name(self): + """ + Test _find_relative(): template_name attribute. + + """ + view = SampleView() + view.template_name = 'new_name' + self._assert_template_location(view, (None, 'new_name.mustache')) + + def test_find_relative__template_extension(self): + """ + Test _find_relative(): template_extension attribute. + + """ + view = SampleView() + view.template_extension = 'txt' + self._assert_template_location(view, (None, 'sample_view.txt')) + + def test_find__with_directory(self): + """ + Test _find() with a view that has a directory specified. + + """ + locator = self._make_locator() + + view = SampleView() + view.template_rel_path = 'foo/bar.txt' + self.assertTrue(locator._find_relative(view)[0] is not None) + + actual = locator._find(view) + expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) + + self.assertEquals(actual, expected) + + def test_find__without_directory(self): + """ + Test _find() with a view that doesn't have a directory specified. + + """ + locator = self._make_locator() + + view = SampleView() + self.assertTrue(locator._find_relative(view)[0] is None) + + actual = locator._find(view) + expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) + + self.assertEquals(actual, expected) + + def _assert_get_template(self, custom, expected): + locator = self._make_locator() + actual = locator.load(custom) + + self.assertEquals(type(actual), unicode) + self.assertEquals(actual, expected) + + def test_get_template(self): + """ + Test get_template(): default behavior (no attributes set). + + """ + view = SampleView() + + self._assert_get_template(view, u"ascii: abc") + + def test_get_template__template_encoding(self): + """ + Test get_template(): template_encoding attribute. + + """ + view = NonAscii() + + self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo') + + view.template_encoding = 'utf-8' + self._assert_get_template(view, u"non-ascii: é") -- cgit v1.2.1 From f9a5b3d2f3b2c615471a780192e4bc4b3ccf4bc8 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 07:02:49 -0700 Subject: Tests now working after making tests directory a sub-package. --- pystache/tests/common.py | 6 +++++- pystache/tests/test_context.py | 2 +- pystache/tests/test_examples.py | 4 ++-- pystache/tests/test_loader.py | 5 +---- pystache/tests/test_locator.py | 4 ++-- pystache/tests/test_renderengine.py | 2 +- pystache/tests/test_renderer.py | 6 +++--- pystache/tests/test_simple.py | 4 ++-- pystache/tests/test_spec.py | 11 ++++++++--- pystache/tests/test_template_spec.py | 15 ++++++++------- 10 files changed, 33 insertions(+), 26 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index c5aa587..e732545 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -8,10 +8,14 @@ Provides test-related code that can be used by all tests. import os import examples +import pystache -DATA_DIR = 'tests/data' +_TESTS_DIR = os.path.dirname(pystache.tests.__file__) + +DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. EXAMPLES_DIR = os.path.dirname(examples.__file__) +SPEC_TEST_DIR = os.path.join(os.path.dirname(pystache.__file__), '..', 'ext', 'spec', 'specs') def get_data_path(file_name): diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py index decf4fb..487ce91 100644 --- a/pystache/tests/test_context.py +++ b/pystache/tests/test_context.py @@ -11,7 +11,7 @@ import unittest from pystache.context import _NOT_FOUND from pystache.context import _get_value from pystache.context import Context -from tests.common import AssertIsMixin +from pystache.tests.common import AssertIsMixin class SimpleObject(object): diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py index 179b089..5f97fa7 100644 --- a/pystache/tests/test_examples.py +++ b/pystache/tests/test_examples.py @@ -12,8 +12,8 @@ from examples.unicode_output import UnicodeOutput from examples.unicode_input import UnicodeInput from examples.nested_context import NestedContext from pystache import Renderer -from tests.common import EXAMPLES_DIR -from tests.common import AssertStringMixin +from pystache.tests.common import EXAMPLES_DIR +from pystache.tests.common import AssertStringMixin class TestView(unittest.TestCase, AssertStringMixin): diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index 119ebef..f300371 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -9,14 +9,11 @@ import os import sys import unittest -from tests.common import AssertStringMixin +from pystache.tests.common import AssertStringMixin, DATA_DIR from pystache import defaults from pystache.loader import Loader -DATA_DIR = 'tests/data' - - class LoaderTests(unittest.TestCase, AssertStringMixin): def test_init__extension(self): diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py index 94a55ad..2261514 100644 --- a/pystache/tests/test_locator.py +++ b/pystache/tests/test_locator.py @@ -1,7 +1,7 @@ # encoding: utf-8 """ -Contains locator.py unit tests. +Unit tests for locator.py. """ @@ -14,7 +14,7 @@ import unittest from pystache.loader import Loader as Reader from pystache.locator import Locator -from tests.common import DATA_DIR +from pystache.tests.common import DATA_DIR from data.views import SayHello diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index 6c2831a..dba950f 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -11,7 +11,7 @@ import unittest from pystache.context import Context from pystache.parser import ParsingError from pystache.renderengine import RenderEngine -from tests.common import AssertStringMixin +from pystache.tests.common import AssertStringMixin class RenderEngineTestCase(unittest.TestCase): diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index a69d11a..c023633 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -15,9 +15,9 @@ from pystache import Renderer from pystache import TemplateSpec from pystache.loader import Loader -from tests.common import get_data_path -from tests.common import AssertStringMixin -from tests.data.views import SayHello +from pystache.tests.common import get_data_path +from pystache.tests.common import AssertStringMixin +from pystache.tests.data.views import SayHello class RendererInitTestCase(unittest.TestCase): diff --git a/pystache/tests/test_simple.py b/pystache/tests/test_simple.py index e19187f..40d3cdc 100644 --- a/pystache/tests/test_simple.py +++ b/pystache/tests/test_simple.py @@ -8,8 +8,8 @@ from examples.lambdas import Lambdas from examples.template_partial import TemplatePartial from examples.simple import Simple -from tests.common import EXAMPLES_DIR -from tests.common import AssertStringMixin +from pystache.tests.common import EXAMPLES_DIR +from pystache.tests.common import AssertStringMixin class TestSimple(unittest.TestCase, AssertStringMixin): diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index 9e7759a..371c642 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -24,12 +24,16 @@ import glob import os.path import unittest +import pystache from pystache.renderer import Renderer -from tests.common import AssertStringMixin +from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR -root_path = os.path.join(os.path.dirname(__file__), '..', 'ext', 'spec', 'specs') -spec_paths = glob.glob(os.path.join(root_path, '*.json')) +spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.json')) + +if len(spec_paths) == 0: + raise Exception("""Spec tests not found in: %s + Consult the README file on how to add the spec tests.""" % repr(SPEC_TEST_DIR)) # TODO: give this a name better than MustacheSpec. @@ -92,6 +96,7 @@ def buildTest(testData, spec_filename): return test + for spec_path in spec_paths: file_name = os.path.basename(spec_path) diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index 9599c37..9368a41 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -19,12 +19,12 @@ from pystache import TemplateSpec from pystache.locator import Locator from pystache.loader import Loader from pystache.spec_loader import SpecLoader -from tests.common import DATA_DIR -from tests.common import EXAMPLES_DIR -from tests.common import AssertIsMixin -from tests.common import AssertStringMixin -from tests.data.views import SampleView -from tests.data.views import NonAscii +from pystache.tests.common import DATA_DIR +from pystache.tests.common import EXAMPLES_DIR +from pystache.tests.common import AssertIsMixin +from pystache.tests.common import AssertStringMixin +from pystache.tests.data.views import SampleView +from pystache.tests.data.views import NonAscii class Thing(object): @@ -46,7 +46,8 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): self.assertRaises(IOError, renderer.render, view) - view.template_rel_directory = "../examples" + # TODO: change this test to remove the following brittle line. + view.template_rel_directory = "../../examples" actual = renderer.render(view) self.assertEquals(actual, "No tags...") -- cgit v1.2.1 From e92e9b3a74027806a8c326ae2640a4a1024f5faf Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 08:13:35 -0700 Subject: Added the README doctests to `python setup.py test`. --- pystache/tests/common.py | 3 ++- pystache/tests/test_doctests.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 pystache/tests/test_doctests.py (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index e732545..758530b 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -15,7 +15,8 @@ _TESTS_DIR = os.path.dirname(pystache.tests.__file__) DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. EXAMPLES_DIR = os.path.dirname(examples.__file__) -SPEC_TEST_DIR = os.path.join(os.path.dirname(pystache.__file__), '..', 'ext', 'spec', 'specs') +PROJECT_DIR = os.path.join(os.path.dirname(pystache.__file__), '..') +SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') def get_data_path(file_name): diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py new file mode 100644 index 0000000..51555ca --- /dev/null +++ b/pystache/tests/test_doctests.py @@ -0,0 +1,37 @@ +# coding: utf-8 + +""" +Creates unittest.TestSuite instances for the doctests in the project. + +""" + +# This module follows the guidance documented here: +# +# http://docs.python.org/library/doctest.html#unittest-api +# + +import os +import doctest +import unittest + +import pystache +from pystache.tests.common import PROJECT_DIR + + +# The paths to text files (i.e. non-module files) containing doctests. +# Paths should be OS-specific and relative to the project directory. +text_file_paths = ['README.rst'] + + +def load_tests(loader, tests, ignore): + # Since module_relative is False in our calls to DocFileSuite below, + # paths should be OS-specific. Moreover, we choose absolute paths + # so that the current working directory does not come into play. + # See the following for more info-- + # + # http://docs.python.org/library/doctest.html#doctest.DocFileSuite + # + paths = [os.path.join(PROJECT_DIR, path) for path in text_file_paths] + tests.addTests(doctest.DocFileSuite(*paths, module_relative=False)) + + return tests -- cgit v1.2.1 From 9b00a932fb6655cf396bc069422a448660f278ad Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 08:37:55 -0700 Subject: Test runner no longer errors out if spec tests not found. --- pystache/tests/test_spec.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index 371c642..c57a743 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -31,15 +31,22 @@ from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.json')) -if len(spec_paths) == 0: - raise Exception("""Spec tests not found in: %s - Consult the README file on how to add the spec tests.""" % repr(SPEC_TEST_DIR)) + +# This test case lets us alert the user that spec tests are missing. +class CheckSpecTestsFound(unittest.TestCase): + + def test_spec_tests_exist(self): + if len(spec_paths) > 0: + return + raise Exception("Spec tests not found in: %s\n " + "Consult the README file on how to add the Mustache spec tests." % repr(SPEC_TEST_DIR)) # TODO: give this a name better than MustacheSpec. class MustacheSpec(unittest.TestCase, AssertStringMixin): pass + def buildTest(testData, spec_filename): name = testData['name'] -- cgit v1.2.1 From f267ff77743b5a289c7bbf760887186bbb431049 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 09:34:04 -0700 Subject: Doctests now included when running `python setup.py test`. --- pystache/tests/common.py | 3 ++- pystache/tests/test_doctests.py | 30 +++++++++++++++++++++++++++++- pystache/tests/test_spec.py | 2 +- 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 758530b..3f5e6dc 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -15,7 +15,8 @@ _TESTS_DIR = os.path.dirname(pystache.tests.__file__) DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. EXAMPLES_DIR = os.path.dirname(examples.__file__) -PROJECT_DIR = os.path.join(os.path.dirname(pystache.__file__), '..') +SOURCE_DIR = os.path.dirname(pystache.__file__) +PROJECT_DIR = os.path.join(SOURCE_DIR, '..') SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 51555ca..6052027 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -12,10 +12,11 @@ Creates unittest.TestSuite instances for the doctests in the project. import os import doctest +import pkgutil import unittest import pystache -from pystache.tests.common import PROJECT_DIR +from pystache.tests.common import PROJECT_DIR, SOURCE_DIR # The paths to text files (i.e. non-module files) containing doctests. @@ -34,4 +35,31 @@ def load_tests(loader, tests, ignore): paths = [os.path.join(PROJECT_DIR, path) for path in text_file_paths] tests.addTests(doctest.DocFileSuite(*paths, module_relative=False)) + modules = get_module_doctests() + for module in modules: + suite = doctest.DocTestSuite(module) + tests.addTests(suite) + return tests + +def get_module_doctests(): + modules = [] + + for pkg in pkgutil.walk_packages([SOURCE_DIR]): + # The importer is a pkgutil.ImpImporter instance: + # + # http://docs.python.org/library/pkgutil.html#pkgutil.ImpImporter + # + importer, module_name, is_package = pkg + if is_package: + # Otherwise, we will get the following error when adding tests: + # + # ValueError: (, 'has no tests') + # + continue + # The loader is a pkgutil.ImpLoader instance. + loader = importer.find_module(module_name) + module = loader.load_module(module_name) + modules.append(module) + + return modules diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index c57a743..e6e9c9a 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -35,7 +35,7 @@ spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.json')) # This test case lets us alert the user that spec tests are missing. class CheckSpecTestsFound(unittest.TestCase): - def test_spec_tests_exist(self): + def test_spec_tests_found(self): if len(spec_paths) > 0: return raise Exception("Spec tests not found in: %s\n " -- cgit v1.2.1 From c491c2fb8a2e9611f29b9fbf189f4bec031152c3 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 09:48:51 -0700 Subject: Tests can now be run with nosetests or Distribute's test. --- pystache/tests/test_doctests.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 6052027..a99b4f9 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -24,7 +24,16 @@ from pystache.tests.common import PROJECT_DIR, SOURCE_DIR text_file_paths = ['README.rst'] -def load_tests(loader, tests, ignore): +# Allowing load_tests() to be called is a hack to allow unit tests +# to be run with nose's nosetests without error. Otherwise, nose +# interprets the following function as a test case, raising the +# following error: +# +# TypeError: load_tests() takes exactly 3 arguments (0 given) +# +def load_tests(loader=None, tests=None, ignore=None): + if loader is None: + return # Since module_relative is False in our calls to DocFileSuite below, # paths should be OS-specific. Moreover, we choose absolute paths # so that the current working directory does not come into play. @@ -42,6 +51,7 @@ def load_tests(loader, tests, ignore): return tests + def get_module_doctests(): modules = [] -- cgit v1.2.1 From 98a5baa0c418cecd81877373c5c01159d4cfcc00 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 09:56:24 -0700 Subject: Added code comments re: the load_tests protocol. --- pystache/tests/test_doctests.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index a99b4f9..99f883d 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -24,10 +24,20 @@ from pystache.tests.common import PROJECT_DIR, SOURCE_DIR text_file_paths = ['README.rst'] -# Allowing load_tests() to be called is a hack to allow unit tests -# to be run with nose's nosetests without error. Otherwise, nose -# interprets the following function as a test case, raising the -# following error: +# The following load_tests() function implements unittests's load_tests +# protocol added in Python 2.7. Using this protocol allows us to +# include the doctests in test runs without the use of nose, for example +# when using Distribute's test as in the following: +# +# python setup.py test +# +# TODO: find a substitute for the load_tests protocol for Python versions +# before version 2.7. +# +# HACK: Allowing load_tests() to be called without arguments is a hack +# to allow unit tests to be run with nose's nosetests without error. +# Otherwise, nose interprets the following function as a test case, +# raising the following error: # # TypeError: load_tests() takes exactly 3 arguments (0 given) # -- cgit v1.2.1 From 7a8aa8f681b9d77fd692aae2e4ab365b3ae2038e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 10:02:45 -0700 Subject: Added a code comment with a link to unittest's load_tests protocol. --- pystache/tests/test_doctests.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 99f883d..15797e1 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -25,9 +25,12 @@ text_file_paths = ['README.rst'] # The following load_tests() function implements unittests's load_tests -# protocol added in Python 2.7. Using this protocol allows us to -# include the doctests in test runs without the use of nose, for example -# when using Distribute's test as in the following: +# protocol added in Python 2.7: +# +# http://docs.python.org/library/unittest.html#load-tests-protocol +# +# Using this protocol lets us include the doctests in test runs without +# using nose, for example when using Distribute's test as in the following: # # python setup.py test # -- cgit v1.2.1 From d88f3153deca6074139db6c299b4574226abefe7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 10:13:35 -0700 Subject: Renamed _to_unicode to _default_to_unicode. --- pystache/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/loader.py b/pystache/loader.py index bcba71b..3506acf 100644 --- a/pystache/loader.py +++ b/pystache/loader.py @@ -12,7 +12,7 @@ from pystache import defaults from pystache.locator import Locator -def _to_unicode(s, encoding=None): +def _default_to_unicode(s, encoding=None): """ Raises a TypeError exception if the given string is already unicode. @@ -67,7 +67,7 @@ class Loader(object): search_dirs = defaults.SEARCH_DIRS if to_unicode is None: - to_unicode = _to_unicode + to_unicode = _default_to_unicode self.extension = extension self.file_encoding = file_encoding -- cgit v1.2.1 From 235157084c9a6edf06f3d6be05dac129a502799b Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 10:44:21 -0700 Subject: Enabled the "use_2to3" keyword argument to setup(). Running `python setup.py test` with Python 2.7 runs all tests except for the spec tests and the doctests in the README file. --- pystache/tests/data/locator/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pystache/tests/data/locator/__init__.py (limited to 'pystache') diff --git a/pystache/tests/data/locator/__init__.py b/pystache/tests/data/locator/__init__.py new file mode 100644 index 0000000..e69de29 -- cgit v1.2.1 From 94203a27b9e44d685e78098105e7e4f288c3fff7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 11:45:47 -0700 Subject: Fixed issue that caused a SyntaxError when running nosetests in Python 2.4. Older versions of Python (2.5 and earlier?) apparently didn't allow keyword arguments to follow positional arguments called with the * operator. --- pystache/tests/test_doctests.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 15797e1..099f4a8 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -55,9 +55,11 @@ def load_tests(loader=None, tests=None, ignore=None): # http://docs.python.org/library/doctest.html#doctest.DocFileSuite # paths = [os.path.join(PROJECT_DIR, path) for path in text_file_paths] - tests.addTests(doctest.DocFileSuite(*paths, module_relative=False)) + for path in paths: + suite = doctest.DocFileSuite(path, module_relative=False) + tests.addTests(suite) - modules = get_module_doctests() + modules = _get_module_doctests() for module in modules: suite = doctest.DocTestSuite(module) tests.addTests(suite) @@ -65,7 +67,7 @@ def load_tests(loader=None, tests=None, ignore=None): return tests -def get_module_doctests(): +def _get_module_doctests(): modules = [] for pkg in pkgutil.walk_packages([SOURCE_DIR]): -- cgit v1.2.1 From 478d740ca8919deb9f8a3c1f8cb6fecb25516366 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 12:01:38 -0700 Subject: Added a TODO re: correcting the test count when running nosetests. --- pystache/tests/test_doctests.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 099f4a8..b9d9895 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -37,6 +37,10 @@ text_file_paths = ['README.rst'] # TODO: find a substitute for the load_tests protocol for Python versions # before version 2.7. # +# TODO: prevent the load_tests() function below from being interpreted as a +# unittest when running nosetests. This is causing the test count to be +# different between `python setup.py test` and `python setup.py nosetests`. +# # HACK: Allowing load_tests() to be called without arguments is a hack # to allow unit tests to be run with nose's nosetests without error. # Otherwise, nose interprets the following function as a test case, -- cgit v1.2.1 From ad539070d0b3032e512f8456c8f7ad37c3555c79 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 12:36:03 -0700 Subject: Cleaned up test_renderengine unit tests related to built-in types. --- pystache/tests/test_renderengine.py | 55 +++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 14 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index dba950f..e83bcde 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -186,35 +186,62 @@ class RenderTests(unittest.TestCase, AssertStringMixin): context = {'test': '{{#hello}}'} self._assert_render(u'{{#hello}}', template, context) + # Built-in types: + # + # Confirm that we not treat instances of built-in types as objects, + # for example by calling a method on a built-in type instance when it + # has a method whose name matches the current key. + # + # Each test case puts an instance of a built-in type on top of the + # context stack before interpolating a tag whose key matches an + # attribute (method or property) of the instance. + # + + def _assert_builtin_attr(self, item, attr_name, expected_attr): + self.assertTrue(hasattr(item, attr_name)) + actual = getattr(item, attr_name) + if callable(actual): + actual = actual() + self.assertEqual(actual, expected_attr) + + def _assert_builtin_type(self, item, attr_name, expected_attr, expected_template): + self._assert_builtin_attr(item, attr_name, expected_attr) + + template = '{{#section}}{{%s}}{{/section}}' % attr_name + context = {'section': item, attr_name: expected_template} + self._assert_render(expected_template, template, context) + def test_interpolation__built_in_type__string(self): """ - Check tag interpolation with a string on the top of the context stack. + Check tag interpolation with a built-in type: string. """ - item = 'abc' - # item.upper() == 'ABC' - template = '{{#section}}{{upper}}{{/section}}' - context = {'section': item, 'upper': 'XYZ'} - self._assert_render(u'XYZ', template, context) + self._assert_builtin_type('abc', 'upper', 'ABC', u'xyz') def test_interpolation__built_in_type__integer(self): """ - Check tag interpolation with an integer on the top of the context stack. + Check tag interpolation with a built-in type: integer. """ - item = 10 - # item.real == 10 - template = '{{#section}}{{real}}{{/section}}' - context = {'section': item, 'real': 1000} - self._assert_render(u'1000', template, context) + # Since public attributes weren't added to integers until Python 2.6 + # (for example the "real" attribute of the numeric type hierarchy)-- + # + # http://docs.python.org/library/numbers.html + # + # we need to resort to built-in attributes (double-underscored) on + # the integer type. + self._assert_builtin_type(15, '__hex__', '0xf', u'999') def test_interpolation__built_in_type__list(self): """ - Check tag interpolation with a list on the top of the context stack. + Check tag interpolation with a built-in type: list. """ item = [[1, 2, 3]] - # item[0].pop() == 3 + attr_name = 'pop' + # Make a copy to prevent changes to item[0]. + self._assert_builtin_attr(list(item[0]), attr_name, 3) + template = '{{#section}}{{pop}}{{/section}}' context = {'section': item, 'pop': 7} self._assert_render(u'7', template, context) -- cgit v1.2.1 From d53165eda52a3b73d1968bcf663ed8d4e0e8ab8b Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 12:46:34 -0700 Subject: Added a RenderEngine test case that strings should not be interpreted as lists. --- pystache/tests/test_renderengine.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index e83bcde..c7ac195 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -242,8 +242,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin): # Make a copy to prevent changes to item[0]. self._assert_builtin_attr(list(item[0]), attr_name, 3) - template = '{{#section}}{{pop}}{{/section}}' - context = {'section': item, 'pop': 7} + template = '{{#section}}{{%s}}{{/section}}' % attr_name + context = {'section': item, attr_name: 7} self._assert_render(u'7', template, context) def test_implicit_iterator__literal(self): @@ -376,6 +376,17 @@ class RenderTests(unittest.TestCase, AssertStringMixin): context = {'section': True, 'template': '{{planet}}', 'planet': 'Earth'} self._assert_render(u'{{planet}}: Earth', template, context) + # TODO: have this test case added to the spec. + def test_section__string_values_not_lists(self): + """ + Check that string section values are not interpreted as lists. + + """ + template = '{{#section}}foo{{/section}}' + context = {'section': '123'} + # If strings were interpreted as lists, this would give "foofoofoo". + self._assert_render(u'foo', template, context) + def test_section__nested_truthy(self): """ Check that "nested truthy" sections get rendered. -- cgit v1.2.1 From 6252e693ae1308fed872e50537f801f4cb65d47c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 13:40:30 -0700 Subject: Test runner now supports both YAML and JSON. --- pystache/tests/test_spec.py | 96 ++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 28 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index e6e9c9a..98b6957 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -11,14 +11,23 @@ FILE_ENCODING = 'utf-8' # the encoding of the spec test files. try: - # We use the JSON files rather than the YAML files because json libraries - # are available for Python 2.4. - import json -except: - # The module json is not available prior to Python 2.6, whereas simplejson is. - # Note that simplejson dropped support for Python 2.4 in simplejson v2.1.0, - # so Python 2.4 requires a simplejson install older than the most recent. - import simplejson as json + import yaml +except ImportError: + try: + # We use the JSON files rather than the YAML files because json libraries + # are available for Python 2.4. + import json + except: + # The module json is not available prior to Python 2.6, whereas simplejson is. + # Note that simplejson dropped support for Python 2.4 in simplejson v2.1.0, + # so Python 2.4 requires a simplejson install older than the most recent. + import simplejson as json + file_extension = 'json' + parser = json +else: + file_extension = 'yml' + parser = yaml + import glob import os.path @@ -29,7 +38,42 @@ from pystache.renderer import Renderer from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR -spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.json')) +spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) + + +def parse(u, file_extension): + """ + Arguments: + + u: a unicode string. + + """ + # Find a cleaner mechanism for choosing between the two. + if file_extension[0] == 'j': + # Then json. + + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # + return json.loads(u) + # Otherwise, yaml. + + def code_constructor(loader, node): + value = loader.construct_mapping(node) + return eval(value['python'], {}) + + yaml.add_constructor(u'!code', code_constructor) + return yaml.load(u) + # This test case lets us alert the user that spec tests are missing. @@ -47,7 +91,13 @@ class MustacheSpec(unittest.TestCase, AssertStringMixin): pass -def buildTest(testData, spec_filename): +def buildTest(testData, spec_filename, parser): + """ + Arguments: + + parser: the module used for parsing (e.g. yaml or json). + + """ name = testData['name'] description = testData['desc'] @@ -57,7 +107,8 @@ def buildTest(testData, spec_filename): def test(self): template = testData['template'] partials = testData.has_key('partials') and testData['partials'] or {} - expected = testData['expected'] + # PyYAML seems to leave ASCII strings as byte strings. + expected = unicode(testData['expected']) data = testData['data'] # Convert code strings to functions. @@ -79,10 +130,10 @@ def buildTest(testData, spec_filename): def escape(s): return s.replace("%", "%%") - subs = [description, template, json.__version__, str(json)] + subs = [description, template, parser.__version__, str(parser)] subs = tuple([escape(sub) for sub in subs]) - # We include the json module version to help in troubleshooting - # json/simplejson issues. + # We include the parsing module version info to help with troubleshooting + # yaml/json/simplejson issues. message = """%s Template: \"""%s\""" @@ -109,31 +160,20 @@ for spec_path in spec_paths: file_name = os.path.basename(spec_path) # We avoid use of the with keyword for Python 2.4 support. + # TODO: share code here with pystache's open() code. f = open(spec_path, 'r') try: s = f.read() finally: f.close() - # The only way to get the simplejson module to return unicode strings - # is to pass it unicode. See, for example-- - # - # http://code.google.com/p/simplejson/issues/detail?id=40 - # - # and the documentation of simplejson.loads(): - # - # "If s is a str then decoded JSON strings that contain only ASCII - # characters may be parsed as str for performance and memory reasons. - # If your code expects only unicode the appropriate solution is - # decode s to unicode prior to calling loads." - # u = s.decode(FILE_ENCODING) - spec_data = json.loads(u) + spec_data = parse(u, file_extension) tests = spec_data['tests'] for test in tests: - test = buildTest(test, file_name) + test = buildTest(test, file_name, parser) setattr(MustacheSpec, test.__name__, test) # Prevent this variable from being interpreted as another test. del(test) -- cgit v1.2.1 From 844ebdbccd0866826809b7b357df3366a7ac1809 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 14:17:45 -0700 Subject: Cleaned up code comments in previous commit. --- pystache/tests/test_spec.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index 98b6957..5ef3cd1 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -11,16 +11,18 @@ FILE_ENCODING = 'utf-8' # the encoding of the spec test files. try: + # We try yaml first since it is more convenient when adding and modifying + # test cases by hand (since the YAML is human-readable and is the master + # from which the JSON format is generated). import yaml except ImportError: try: - # We use the JSON files rather than the YAML files because json libraries - # are available for Python 2.4. import json except: - # The module json is not available prior to Python 2.6, whereas simplejson is. - # Note that simplejson dropped support for Python 2.4 in simplejson v2.1.0, - # so Python 2.4 requires a simplejson install older than the most recent. + # The module json is not available prior to Python 2.6, whereas + # simplejson is. The simplejson package dropped support for Python 2.4 + # in simplejson v2.1.0, so Python 2.4 requires a simplejson install + # older than the most recent version. import simplejson as json file_extension = 'json' parser = json @@ -48,7 +50,7 @@ def parse(u, file_extension): u: a unicode string. """ - # Find a cleaner mechanism for choosing between the two. + # TODO: find a cleaner mechanism for choosing between the two. if file_extension[0] == 'j': # Then json. @@ -75,7 +77,6 @@ def parse(u, file_extension): return yaml.load(u) - # This test case lets us alert the user that spec tests are missing. class CheckSpecTestsFound(unittest.TestCase): -- cgit v1.2.1 From da8a3b891cb0aaeadcff14a367e21cfcc63608a6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 5 Apr 2012 05:58:11 -0700 Subject: Switched to using codecs.open() in tests/test_spec.py for Python 3 support. --- pystache/tests/test_spec.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py index 5ef3cd1..06e4dd8 100644 --- a/pystache/tests/test_spec.py +++ b/pystache/tests/test_spec.py @@ -31,6 +31,7 @@ else: parser = yaml +import codecs import glob import os.path import unittest @@ -160,15 +161,33 @@ for spec_path in spec_paths: file_name = os.path.basename(spec_path) - # We avoid use of the with keyword for Python 2.4 support. + # We use codecs.open() for pre Python 2.6 support and because it ports + # correctly to Python 3: + # + # "If pre-2.6 compatibility is needed, then you should use codecs.open() + # instead. This will make sure that you get back unicode strings in Python 2." + # + # (from http://docs.python.org/py3k/howto/pyporting.html#text-files ) + # # TODO: share code here with pystache's open() code. - f = open(spec_path, 'r') + f = codecs.open(spec_path, 'r', encoding=FILE_ENCODING) try: - s = f.read() + u = f.read() finally: f.close() - u = s.decode(FILE_ENCODING) + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # spec_data = parse(u, file_extension) tests = spec_data['tests'] -- cgit v1.2.1 From 93a58fc02e2b0b990147ab6cd42b9e3a3a4be3b1 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 5 Apr 2012 06:22:27 -0700 Subject: Python 3 support: switched assertEquals() to assertEqual(). The assertEquals() function is deperecated in Python 3.2: http://docs.python.org/py3k/library/unittest.html#deprecated-aliases --- pystache/tests/common.py | 4 +- pystache/tests/test_commands.py | 2 +- pystache/tests/test_context.py | 90 +++++++++++++------------- pystache/tests/test_examples.py | 2 +- pystache/tests/test_loader.py | 10 +-- pystache/tests/test_locator.py | 32 +++++----- pystache/tests/test_pystache.py | 8 +-- pystache/tests/test_renderengine.py | 10 +-- pystache/tests/test_renderer.py | 120 +++++++++++++++++------------------ pystache/tests/test_simple.py | 6 +- pystache/tests/test_template_spec.py | 28 ++++---- 11 files changed, 156 insertions(+), 156 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 3f5e6dc..538784b 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -55,10 +55,10 @@ class AssertStringMixin: description = details % reason return format % description - self.assertEquals(actual, expected, make_message("different characters")) + self.assertEqual(actual, expected, make_message("different characters")) reason = "types different: %s != %s (actual)" % (repr(type(expected)), repr(type(actual))) - self.assertEquals(type(expected), type(actual), make_message(reason)) + self.assertEqual(type(expected), type(actual), make_message(reason)) class AssertIsMixin: diff --git a/pystache/tests/test_commands.py b/pystache/tests/test_commands.py index f1817e7..fc11f30 100644 --- a/pystache/tests/test_commands.py +++ b/pystache/tests/test_commands.py @@ -39,7 +39,7 @@ class CommandsTestCase(unittest.TestCase): """ actual = self.callScript("Hi {{thing}}", '{"thing": "world"}') - self.assertEquals(actual, u"Hi world\n") + self.assertEqual(actual, u"Hi world\n") def tearDown(self): sys.stdout = ORIGINAL_STDOUT diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py index 487ce91..2b6846f 100644 --- a/pystache/tests/test_context.py +++ b/pystache/tests/test_context.py @@ -58,7 +58,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = {"foo": "bar"} - self.assertEquals(_get_value(item, "foo"), "bar") + self.assertEqual(_get_value(item, "foo"), "bar") def test_dictionary__callable_not_called(self): """ @@ -87,7 +87,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = {} attr_name = "keys" - self.assertEquals(getattr(item, attr_name)(), []) + self.assertEqual(getattr(item, attr_name)(), []) self.assertNotFound(item, attr_name) def test_dictionary__dict_subclass(self): @@ -100,7 +100,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): item = DictSubclass() item["foo"] = "bar" - self.assertEquals(_get_value(item, "foo"), "bar") + self.assertEqual(_get_value(item, "foo"), "bar") ### Case: the item is an object. @@ -110,7 +110,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = SimpleObject() - self.assertEquals(_get_value(item, "foo"), "bar") + self.assertEqual(_get_value(item, "foo"), "bar") def test_object__attribute_missing(self): """ @@ -126,7 +126,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = SimpleObject() - self.assertEquals(_get_value(item, "foo_callable"), "called...") + self.assertEqual(_get_value(item, "foo_callable"), "called...") def test_object__non_built_in_type(self): """ @@ -134,7 +134,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = datetime(2012, 1, 2) - self.assertEquals(_get_value(item, "day"), 2) + self.assertEqual(_get_value(item, "day"), 2) def test_object__dict_like(self): """ @@ -142,7 +142,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ item = DictLike() - self.assertEquals(item["foo"], "bar") + self.assertEqual(item["foo"], "bar") self.assertNotFound(item, "foo") ### Case: the item is an instance of a built-in type. @@ -168,10 +168,10 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): # return - self.assertEquals(item1.real, 10) - self.assertEquals(item2.real, 10) + self.assertEqual(item1.real, 10) + self.assertEqual(item2.real, 10) - self.assertEquals(_get_value(item1, 'real'), 10) + self.assertEqual(_get_value(item1, 'real'), 10) self.assertNotFound(item2, 'real') def test_built_in_type__string(self): @@ -184,10 +184,10 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): item1 = MyStr('abc') item2 = 'abc' - self.assertEquals(item1.upper(), 'ABC') - self.assertEquals(item2.upper(), 'ABC') + self.assertEqual(item1.upper(), 'ABC') + self.assertEqual(item2.upper(), 'ABC') - self.assertEquals(_get_value(item1, 'upper'), 'ABC') + self.assertEqual(_get_value(item1, 'upper'), 'ABC') self.assertNotFound(item2, 'upper') def test_built_in_type__list(self): @@ -200,10 +200,10 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): item1 = MyList([1, 2, 3]) item2 = [1, 2, 3] - self.assertEquals(item1.pop(), 3) - self.assertEquals(item2.pop(), 3) + self.assertEqual(item1.pop(), 3) + self.assertEqual(item2.pop(), 3) - self.assertEquals(_get_value(item1, 'pop'), 2) + self.assertEqual(_get_value(item1, 'pop'), 2) self.assertNotFound(item2, 'pop') @@ -230,23 +230,23 @@ class ContextTests(unittest.TestCase, AssertIsMixin): def test__repr(self): context = Context() - self.assertEquals(repr(context), 'Context()') + self.assertEqual(repr(context), 'Context()') context = Context({'foo': 'bar'}) - self.assertEquals(repr(context), "Context({'foo': 'bar'},)") + self.assertEqual(repr(context), "Context({'foo': 'bar'},)") context = Context({'foo': 'bar'}, {'abc': 123}) - self.assertEquals(repr(context), "Context({'foo': 'bar'}, {'abc': 123})") + self.assertEqual(repr(context), "Context({'foo': 'bar'}, {'abc': 123})") def test__str(self): context = Context() - self.assertEquals(str(context), 'Context()') + self.assertEqual(str(context), 'Context()') context = Context({'foo': 'bar'}) - self.assertEquals(str(context), "Context({'foo': 'bar'},)") + self.assertEqual(str(context), "Context({'foo': 'bar'},)") context = Context({'foo': 'bar'}, {'abc': 123}) - self.assertEquals(str(context), "Context({'foo': 'bar'}, {'abc': 123})") + self.assertEqual(str(context), "Context({'foo': 'bar'}, {'abc': 123})") ## Test the static create() method. @@ -256,7 +256,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context.create({'foo': 'bar'}) - self.assertEquals(context.get('foo'), 'bar') + self.assertEqual(context.get('foo'), 'bar') def test_create__none(self): """ @@ -264,7 +264,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context.create({'foo': 'bar'}, None) - self.assertEquals(context.get('foo'), 'bar') + self.assertEqual(context.get('foo'), 'bar') def test_create__object(self): """ @@ -274,7 +274,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): class Foo(object): foo = 'bar' context = Context.create(Foo()) - self.assertEquals(context.get('foo'), 'bar') + self.assertEqual(context.get('foo'), 'bar') def test_create__context(self): """ @@ -283,7 +283,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ obj = Context({'foo': 'bar'}) context = Context.create(obj) - self.assertEquals(context.get('foo'), 'bar') + self.assertEqual(context.get('foo'), 'bar') def test_create__kwarg(self): """ @@ -291,7 +291,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context.create(foo='bar') - self.assertEquals(context.get('foo'), 'bar') + self.assertEqual(context.get('foo'), 'bar') def test_create__precedence_positional(self): """ @@ -299,7 +299,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context.create({'foo': 'bar'}, {'foo': 'buzz'}) - self.assertEquals(context.get('foo'), 'buzz') + self.assertEqual(context.get('foo'), 'buzz') def test_create__precedence_keyword(self): """ @@ -307,7 +307,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context.create({'foo': 'bar'}, foo='buzz') - self.assertEquals(context.get('foo'), 'buzz') + self.assertEqual(context.get('foo'), 'buzz') def test_get__key_present(self): """ @@ -315,7 +315,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context({"foo": "bar"}) - self.assertEquals(context.get("foo"), "bar") + self.assertEqual(context.get("foo"), "bar") def test_get__key_missing(self): """ @@ -331,7 +331,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context() - self.assertEquals(context.get("foo", "bar"), "bar") + self.assertEqual(context.get("foo", "bar"), "bar") def test_get__precedence(self): """ @@ -339,7 +339,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context({"foo": "bar"}, {"foo": "buzz"}) - self.assertEquals(context.get("foo"), "buzz") + self.assertEqual(context.get("foo"), "buzz") def test_get__fallback(self): """ @@ -347,7 +347,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ context = Context({"fuzz": "buzz"}, {"foo": "bar"}) - self.assertEquals(context.get("fuzz"), "buzz") + self.assertEqual(context.get("fuzz"), "buzz") def test_push(self): """ @@ -356,10 +356,10 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ key = "foo" context = Context({key: "bar"}) - self.assertEquals(context.get(key), "bar") + self.assertEqual(context.get(key), "bar") context.push({key: "buzz"}) - self.assertEquals(context.get(key), "buzz") + self.assertEqual(context.get(key), "buzz") def test_pop(self): """ @@ -368,33 +368,33 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ key = "foo" context = Context({key: "bar"}, {key: "buzz"}) - self.assertEquals(context.get(key), "buzz") + self.assertEqual(context.get(key), "buzz") item = context.pop() - self.assertEquals(item, {"foo": "buzz"}) - self.assertEquals(context.get(key), "bar") + self.assertEqual(item, {"foo": "buzz"}) + self.assertEqual(context.get(key), "bar") def test_top(self): key = "foo" context = Context({key: "bar"}, {key: "buzz"}) - self.assertEquals(context.get(key), "buzz") + self.assertEqual(context.get(key), "buzz") top = context.top() - self.assertEquals(top, {"foo": "buzz"}) + self.assertEqual(top, {"foo": "buzz"}) # Make sure calling top() didn't remove the item from the stack. - self.assertEquals(context.get(key), "buzz") + self.assertEqual(context.get(key), "buzz") def test_copy(self): key = "foo" original = Context({key: "bar"}, {key: "buzz"}) - self.assertEquals(original.get(key), "buzz") + self.assertEqual(original.get(key), "buzz") new = original.copy() # Confirm that the copy behaves the same. - self.assertEquals(new.get(key), "buzz") + self.assertEqual(new.get(key), "buzz") # Change the copy, and confirm it is changed. new.pop() - self.assertEquals(new.get(key), "bar") + self.assertEqual(new.get(key), "bar") # Confirm the original is unchanged. - self.assertEquals(original.get(key), "buzz") + self.assertEqual(original.get(key), "buzz") diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py index 5f97fa7..d99478f 100644 --- a/pystache/tests/test_examples.py +++ b/pystache/tests/test_examples.py @@ -95,7 +95,7 @@ Again, Welcome!""") view.template = '''{{>partial_in_partial}}''' actual = renderer.render(view, {'prop': 'derp'}) - self.assertEquals(actual, 'Hi derp!') + self.assertEqual(actual, 'Hi derp!') if __name__ == '__main__': unittest.main() diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index f300371..efd09c5 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -18,30 +18,30 @@ class LoaderTests(unittest.TestCase, AssertStringMixin): def test_init__extension(self): loader = Loader(extension='foo') - self.assertEquals(loader.extension, 'foo') + self.assertEqual(loader.extension, 'foo') def test_init__extension__default(self): # Test the default value. loader = Loader() - self.assertEquals(loader.extension, 'mustache') + self.assertEqual(loader.extension, 'mustache') def test_init__file_encoding(self): loader = Loader(file_encoding='bar') - self.assertEquals(loader.file_encoding, 'bar') + self.assertEqual(loader.file_encoding, 'bar') def test_init__file_encoding__default(self): file_encoding = defaults.FILE_ENCODING try: defaults.FILE_ENCODING = 'foo' loader = Loader() - self.assertEquals(loader.file_encoding, 'foo') + self.assertEqual(loader.file_encoding, 'foo') finally: defaults.FILE_ENCODING = file_encoding def test_init__to_unicode(self): to_unicode = lambda x: x loader = Loader(to_unicode=to_unicode) - self.assertEquals(loader.to_unicode, to_unicode) + self.assertEqual(loader.to_unicode, to_unicode) def test_init__to_unicode__default(self): loader = Loader() diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py index 2261514..2ae07ca 100644 --- a/pystache/tests/test_locator.py +++ b/pystache/tests/test_locator.py @@ -26,10 +26,10 @@ class LocatorTests(unittest.TestCase): def test_init__extension(self): # Test the default value. locator = Locator() - self.assertEquals(locator.template_extension, 'mustache') + self.assertEqual(locator.template_extension, 'mustache') locator = Locator(extension='txt') - self.assertEquals(locator.template_extension, 'txt') + self.assertEqual(locator.template_extension, 'txt') locator = Locator(extension=False) self.assertTrue(locator.template_extension is False) @@ -40,40 +40,40 @@ class LocatorTests(unittest.TestCase): obj = SayHello() actual = locator.get_object_directory(obj) - self.assertEquals(actual, os.path.abspath(DATA_DIR)) + self.assertEqual(actual, os.path.abspath(DATA_DIR)) def test_get_object_directory__not_hasattr_module(self): locator = Locator() obj = datetime(2000, 1, 1) self.assertFalse(hasattr(obj, '__module__')) - self.assertEquals(locator.get_object_directory(obj), None) + self.assertEqual(locator.get_object_directory(obj), None) self.assertFalse(hasattr(None, '__module__')) - self.assertEquals(locator.get_object_directory(None), None) + self.assertEqual(locator.get_object_directory(None), None) def test_make_file_name(self): locator = Locator() locator.template_extension = 'bar' - self.assertEquals(locator.make_file_name('foo'), 'foo.bar') + self.assertEqual(locator.make_file_name('foo'), 'foo.bar') locator.template_extension = False - self.assertEquals(locator.make_file_name('foo'), 'foo') + self.assertEqual(locator.make_file_name('foo'), 'foo') locator.template_extension = '' - self.assertEquals(locator.make_file_name('foo'), 'foo.') + self.assertEqual(locator.make_file_name('foo'), 'foo.') def test_make_file_name__template_extension_argument(self): locator = Locator() - self.assertEquals(locator.make_file_name('foo', template_extension='bar'), 'foo.bar') + self.assertEqual(locator.make_file_name('foo', template_extension='bar'), 'foo.bar') def test_find_name(self): locator = Locator() path = locator.find_name(search_dirs=['examples'], template_name='simple') - self.assertEquals(os.path.basename(path), 'simple.mustache') + self.assertEqual(os.path.basename(path), 'simple.mustache') def test_find_name__using_list_of_paths(self): locator = Locator() @@ -98,7 +98,7 @@ class LocatorTests(unittest.TestCase): dirpath = os.path.dirname(path) dirname = os.path.split(dirpath)[-1] - self.assertEquals(dirname, 'locator') + self.assertEqual(dirname, 'locator') def test_find_name__non_existent_template_fails(self): locator = Locator() @@ -113,7 +113,7 @@ class LocatorTests(unittest.TestCase): actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache') expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_find_object__none_file_name(self): locator = Locator() @@ -123,18 +123,18 @@ class LocatorTests(unittest.TestCase): actual = locator.find_object(search_dirs=[], obj=obj) expected = os.path.abspath(os.path.join(DATA_DIR, 'say_hello.mustache')) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_find_object__none_object_directory(self): locator = Locator() obj = None - self.assertEquals(None, locator.get_object_directory(obj)) + self.assertEqual(None, locator.get_object_directory(obj)) actual = locator.find_object(search_dirs=[DATA_DIR], obj=obj, file_name='say_hello.mustache') expected = os.path.join(DATA_DIR, 'say_hello.mustache') - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_make_template_name(self): """ @@ -147,4 +147,4 @@ class LocatorTests(unittest.TestCase): pass foo = FooBar() - self.assertEquals(locator.make_template_name(foo), 'foo_bar') + self.assertEqual(locator.make_template_name(foo), 'foo_bar') diff --git a/pystache/tests/test_pystache.py b/pystache/tests/test_pystache.py index f9857cd..e241842 100644 --- a/pystache/tests/test_pystache.py +++ b/pystache/tests/test_pystache.py @@ -9,15 +9,15 @@ class PystacheTests(unittest.TestCase): def _assert_rendered(self, expected, template, context): actual = pystache.render(template, context) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_basic(self): ret = pystache.render("Hi {{thing}}!", { 'thing': 'world' }) - self.assertEquals(ret, "Hi world!") + self.assertEqual(ret, "Hi world!") def test_kwargs(self): ret = pystache.render("Hi {{thing}}!", thing='world') - self.assertEquals(ret, "Hi world!") + self.assertEqual(ret, "Hi world!") def test_less_basic(self): template = "It's a nice day for {{beverage}}, right {{person}}?" @@ -42,7 +42,7 @@ class PystacheTests(unittest.TestCase): def test_comments(self): template = "What {{! the }} what?" actual = pystache.render(template) - self.assertEquals("What what?", actual) + self.assertEqual("What what?", actual) def test_false_sections_are_hidden(self): template = "Ready {{#set}}set {{/set}}go!" diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index c7ac195..2d2582b 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -26,9 +26,9 @@ class RenderEngineTestCase(unittest.TestCase): # In real-life, these arguments would be functions engine = RenderEngine(load_partial="foo", literal="literal", escape="escape") - self.assertEquals(engine.escape, "escape") - self.assertEquals(engine.literal, "literal") - self.assertEquals(engine.load_partial, "foo") + self.assertEqual(engine.escape, "escape") + self.assertEqual(engine.literal, "literal") + self.assertEqual(engine.load_partial, "foo") class RenderTests(unittest.TestCase, AssertStringMixin): @@ -315,7 +315,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): try: self._assert_render(None, template) except ParsingError, err: - self.assertEquals(str(err), "Section end tag mismatch: u'section' != None") + self.assertEqual(str(err), "Section end tag mismatch: u'section' != None") def test_section__end_tag_mismatch(self): """ @@ -326,7 +326,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): try: self._assert_render(None, template) except ParsingError, err: - self.assertEquals(str(err), "Section end tag mismatch: u'section_end' != u'section_start'") + self.assertEqual(str(err), "Section end tag mismatch: u'section_end' != u'section_start'") def test_section__context_values(self): """ diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index c023633..3636bd5 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -41,20 +41,20 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(partials={'foo': 'bar'}) - self.assertEquals(renderer.partials, {'foo': 'bar'}) + self.assertEqual(renderer.partials, {'foo': 'bar'}) def test_escape__default(self): escape = Renderer().escape - self.assertEquals(escape(">"), ">") - self.assertEquals(escape('"'), """) + self.assertEqual(escape(">"), ">") + self.assertEqual(escape('"'), """) # Single quotes are not escaped. - self.assertEquals(escape("'"), "'") + self.assertEqual(escape("'"), "'") def test_escape(self): escape = lambda s: "**" + s renderer = Renderer(escape=escape) - self.assertEquals(renderer.escape("bar"), "**bar") + self.assertEqual(renderer.escape("bar"), "**bar") def test_decode_errors__default(self): """ @@ -62,7 +62,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer() - self.assertEquals(renderer.decode_errors, 'strict') + self.assertEqual(renderer.decode_errors, 'strict') def test_decode_errors(self): """ @@ -70,7 +70,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(decode_errors="foo") - self.assertEquals(renderer.decode_errors, "foo") + self.assertEqual(renderer.decode_errors, "foo") def test_file_encoding__default(self): """ @@ -78,7 +78,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer() - self.assertEquals(renderer.file_encoding, renderer.string_encoding) + self.assertEqual(renderer.file_encoding, renderer.string_encoding) def test_file_encoding(self): """ @@ -86,7 +86,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(file_encoding='foo') - self.assertEquals(renderer.file_encoding, 'foo') + self.assertEqual(renderer.file_encoding, 'foo') def test_file_extension__default(self): """ @@ -94,7 +94,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer() - self.assertEquals(renderer.file_extension, 'mustache') + self.assertEqual(renderer.file_extension, 'mustache') def test_file_extension(self): """ @@ -102,7 +102,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(file_extension='foo') - self.assertEquals(renderer.file_extension, 'foo') + self.assertEqual(renderer.file_extension, 'foo') def test_search_dirs__default(self): """ @@ -110,7 +110,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer() - self.assertEquals(renderer.search_dirs, [os.curdir]) + self.assertEqual(renderer.search_dirs, [os.curdir]) def test_search_dirs__string(self): """ @@ -118,7 +118,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(search_dirs='foo') - self.assertEquals(renderer.search_dirs, ['foo']) + self.assertEqual(renderer.search_dirs, ['foo']) def test_search_dirs__list(self): """ @@ -126,7 +126,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(search_dirs=['foo']) - self.assertEquals(renderer.search_dirs, ['foo']) + self.assertEqual(renderer.search_dirs, ['foo']) def test_string_encoding__default(self): """ @@ -134,7 +134,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer() - self.assertEquals(renderer.string_encoding, sys.getdefaultencoding()) + self.assertEqual(renderer.string_encoding, sys.getdefaultencoding()) def test_string_encoding(self): """ @@ -142,7 +142,7 @@ class RendererInitTestCase(unittest.TestCase): """ renderer = Renderer(string_encoding="foo") - self.assertEquals(renderer.string_encoding, "foo") + self.assertEqual(renderer.string_encoding, "foo") class RendererTests(unittest.TestCase, AssertStringMixin): @@ -166,7 +166,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): self.assertRaises(UnicodeDecodeError, renderer.unicode, s) renderer.string_encoding = "utf-8" - self.assertEquals(renderer.unicode(s), u"é") + self.assertEqual(renderer.unicode(s), u"é") def test_unicode__decode_errors(self): """ @@ -178,11 +178,11 @@ class RendererTests(unittest.TestCase, AssertStringMixin): s = "déf" renderer.decode_errors = "ignore" - self.assertEquals(renderer.unicode(s), "df") + self.assertEqual(renderer.unicode(s), "df") renderer.decode_errors = "replace" # U+FFFD is the official Unicode replacement character. - self.assertEquals(renderer.unicode(s), u'd\ufffd\ufffdf') + self.assertEqual(renderer.unicode(s), u'd\ufffd\ufffdf') ## Test the _make_loader() method. @@ -194,7 +194,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): renderer = Renderer() loader = renderer._make_loader() - self.assertEquals(type(loader), Loader) + self.assertEqual(type(loader), Loader) def test__make_loader__attributes(self): """ @@ -210,9 +210,9 @@ class RendererTests(unittest.TestCase, AssertStringMixin): loader = renderer._make_loader() - self.assertEquals(loader.extension, 'ext') - self.assertEquals(loader.file_encoding, 'enc') - self.assertEquals(loader.to_unicode, unicode_) + self.assertEqual(loader.extension, 'ext') + self.assertEqual(loader.file_encoding, 'enc') + self.assertEqual(loader.to_unicode, unicode_) ## Test the render() method. @@ -223,22 +223,22 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() rendered = renderer.render('foo') - self.assertEquals(type(rendered), unicode) + self.assertEqual(type(rendered), unicode) def test_render__unicode(self): renderer = Renderer() actual = renderer.render(u'foo') - self.assertEquals(actual, u'foo') + self.assertEqual(actual, u'foo') def test_render__str(self): renderer = Renderer() actual = renderer.render('foo') - self.assertEquals(actual, 'foo') + self.assertEqual(actual, 'foo') def test_render__non_ascii_character(self): renderer = Renderer() actual = renderer.render(u'Poincaré') - self.assertEquals(actual, u'Poincaré') + self.assertEqual(actual, u'Poincaré') def test_render__context(self): """ @@ -246,7 +246,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() - self.assertEquals(renderer.render('Hi {{person}}', {'person': 'Mom'}), 'Hi Mom') + self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}), 'Hi Mom') def test_render__context_and_kwargs(self): """ @@ -255,7 +255,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() template = 'Hi {{person1}} and {{person2}}' - self.assertEquals(renderer.render(template, {'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad') + self.assertEqual(renderer.render(template, {'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad') def test_render__kwargs_and_no_context(self): """ @@ -263,7 +263,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() - self.assertEquals(renderer.render('Hi {{person}}', person='Mom'), 'Hi Mom') + self.assertEqual(renderer.render('Hi {{person}}', person='Mom'), 'Hi Mom') def test_render__context_and_kwargs__precedence(self): """ @@ -271,7 +271,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() - self.assertEquals(renderer.render('Hi {{person}}', {'person': 'Mom'}, person='Dad'), 'Hi Dad') + self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}, person='Dad'), 'Hi Dad') def test_render__kwargs_does_not_modify_context(self): """ @@ -281,7 +281,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): context = {} renderer = Renderer() renderer.render('Hi {{person}}', context=context, foo="bar") - self.assertEquals(context, {}) + self.assertEqual(context, {}) def test_render__nonascii_template(self): """ @@ -294,10 +294,10 @@ class RendererTests(unittest.TestCase, AssertStringMixin): # Check that decode_errors and string_encoding are both respected. renderer.decode_errors = 'ignore' renderer.string_encoding = 'ascii' - self.assertEquals(renderer.render(template), "df") + self.assertEqual(renderer.render(template), "df") renderer.string_encoding = 'utf_8' - self.assertEquals(renderer.render(template), u"déf") + self.assertEqual(renderer.render(template), u"déf") def test_make_load_partial(self): """ @@ -309,8 +309,8 @@ class RendererTests(unittest.TestCase, AssertStringMixin): load_partial = renderer._make_load_partial() actual = load_partial('foo') - self.assertEquals(actual, 'bar') - self.assertEquals(type(actual), unicode, "RenderEngine requires that " + self.assertEqual(actual, 'bar') + self.assertEqual(type(actual), unicode, "RenderEngine requires that " "load_partial return unicode strings.") def test_make_load_partial__unicode(self): @@ -322,14 +322,14 @@ class RendererTests(unittest.TestCase, AssertStringMixin): renderer.partials = {'partial': 'foo'} load_partial = renderer._make_load_partial() - self.assertEquals(load_partial("partial"), "foo") + self.assertEqual(load_partial("partial"), "foo") # Now with a value that is already unicode. renderer.partials = {'partial': u'foo'} load_partial = renderer._make_load_partial() # If the next line failed, we would get the following error: # TypeError: decoding Unicode is not supported - self.assertEquals(load_partial("partial"), "foo") + self.assertEqual(load_partial("partial"), "foo") def test_render_path(self): """ @@ -339,7 +339,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): renderer = Renderer() path = get_data_path('say_hello.mustache') actual = renderer.render_path(path, to='foo') - self.assertEquals(actual, "Hello, foo") + self.assertEqual(actual, "Hello, foo") def test_render__object(self): """ @@ -350,10 +350,10 @@ class RendererTests(unittest.TestCase, AssertStringMixin): say_hello = SayHello() actual = renderer.render(say_hello) - self.assertEquals('Hello, World', actual) + self.assertEqual('Hello, World', actual) actual = renderer.render(say_hello, to='Mars') - self.assertEquals('Hello, Mars', actual) + self.assertEqual('Hello, Mars', actual) def test_render__template_spec(self): """ @@ -379,7 +379,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): view = Simple() actual = renderer.render(view) - self.assertEquals('Hi pizza!', actual) + self.assertEqual('Hi pizza!', actual) # By testing that Renderer.render() constructs the right RenderEngine, @@ -410,13 +410,13 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() actual = engine.load_partial('str') - self.assertEquals(actual, "foo") - self.assertEquals(type(actual), unicode) + self.assertEqual(actual, "foo") + self.assertEqual(type(actual), unicode) # Check that unicode subclasses are not preserved. actual = engine.load_partial('subclass') - self.assertEquals(actual, "abc") - self.assertEquals(type(actual), unicode) + self.assertEqual(actual, "abc") + self.assertEqual(type(actual), unicode) def test__load_partial__not_found(self): """ @@ -433,7 +433,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): load_partial("foo") raise Exception("Shouldn't get here") except Exception, err: - self.assertEquals(str(err), "Partial not found with name: 'foo'") + self.assertEqual(str(err), "Partial not found with name: 'foo'") ## Test the engine's literal attribute. @@ -448,7 +448,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() literal = engine.literal - self.assertEquals(literal("foo"), "FOO") + self.assertEqual(literal("foo"), "FOO") def test__literal__handles_unicode(self): """ @@ -461,7 +461,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() literal = engine.literal - self.assertEquals(literal(u"foo"), "foo") + self.assertEqual(literal(u"foo"), "foo") def test__literal__returns_unicode(self): """ @@ -474,16 +474,16 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() literal = engine.literal - self.assertEquals(type(literal("foo")), unicode) + self.assertEqual(type(literal("foo")), unicode) class MyUnicode(unicode): pass s = MyUnicode("abc") - self.assertEquals(type(s), MyUnicode) + self.assertEqual(type(s), MyUnicode) self.assertTrue(isinstance(s, unicode)) - self.assertEquals(type(literal(s)), unicode) + self.assertEqual(type(literal(s)), unicode) ## Test the engine's escape attribute. @@ -498,7 +498,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() escape = engine.escape - self.assertEquals(escape("foo"), "**foo") + self.assertEqual(escape("foo"), "**foo") def test__escape__uses_renderer_unicode(self): """ @@ -511,7 +511,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() escape = engine.escape - self.assertEquals(escape("foo"), "FOO") + self.assertEqual(escape("foo"), "FOO") def test__escape__has_access_to_original_unicode_subclass(self): """ @@ -527,9 +527,9 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): class MyUnicode(unicode): pass - self.assertEquals(escape("foo"), "unicode") - self.assertEquals(escape(u"foo"), "unicode") - self.assertEquals(escape(MyUnicode("foo")), "MyUnicode") + self.assertEqual(escape("foo"), "unicode") + self.assertEqual(escape(u"foo"), "unicode") + self.assertEqual(escape(MyUnicode("foo")), "MyUnicode") def test__escape__returns_unicode(self): """ @@ -542,7 +542,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): engine = renderer._make_render_engine() escape = engine.escape - self.assertEquals(type(escape("foo")), unicode) + self.assertEqual(type(escape("foo")), unicode) # Check that literal doesn't preserve unicode subclasses. class MyUnicode(unicode): @@ -550,7 +550,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): s = MyUnicode("abc") - self.assertEquals(type(s), MyUnicode) + self.assertEqual(type(s), MyUnicode) self.assertTrue(isinstance(s, unicode)) - self.assertEquals(type(escape(s)), unicode) + self.assertEqual(type(escape(s)), unicode) diff --git a/pystache/tests/test_simple.py b/pystache/tests/test_simple.py index 40d3cdc..07b059f 100644 --- a/pystache/tests/test_simple.py +++ b/pystache/tests/test_simple.py @@ -28,11 +28,11 @@ class TestSimple(unittest.TestCase, AssertStringMixin): renderer = Renderer() actual = renderer.render(template, context) - self.assertEquals(actual, "Colors: red Colors: green Colors: blue ") + self.assertEqual(actual, "Colors: red Colors: green Colors: blue ") def test_empty_context(self): template = '{{#empty_list}}Shouldnt see me {{/empty_list}}{{^empty_list}}Should see me{{/empty_list}}' - self.assertEquals(pystache.Renderer().render(template), "Should see me") + self.assertEqual(pystache.Renderer().render(template), "Should see me") def test_callables(self): view = Lambdas() @@ -58,7 +58,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin): def test_non_existent_value_renders_blank(self): view = Simple() template = '{{not_set}} {{blank}}' - self.assertEquals(pystache.Renderer().render(template), ' ') + self.assertEqual(pystache.Renderer().render(template), ' ') def test_template_partial_extension(self): diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index 9368a41..7c606c3 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -49,7 +49,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): # TODO: change this test to remove the following brittle line. view.template_rel_directory = "../../examples" actual = renderer.render(view) - self.assertEquals(actual, "No tags...") + self.assertEqual(actual, "No tags...") def test_template_path_for_partials(self): """ @@ -65,7 +65,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): self.assertRaises(IOError, renderer1.render, spec) actual = renderer2.render(spec) - self.assertEquals(actual, "Partial: No tags...") + self.assertEqual(actual, "Partial: No tags...") def test_basic_method_calls(self): renderer = Renderer() @@ -79,7 +79,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): renderer = Renderer() actual = renderer.render(view) - self.assertEquals(actual, "Hi Chris!") + self.assertEqual(actual, "Hi Chris!") def test_complex(self): renderer = Renderer() @@ -95,7 +95,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): def test_higher_order_replace(self): renderer = Renderer() actual = renderer.render(Lambdas()) - self.assertEquals(actual, 'bar != bar. oh, it does!') + self.assertEqual(actual, 'bar != bar. oh, it does!') def test_higher_order_rot13(self): view = Lambdas() @@ -119,7 +119,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): renderer = Renderer(search_dirs=EXAMPLES_DIR) actual = renderer.render(view) - self.assertEquals(actual, u'nopqrstuvwxyz') + self.assertEqual(actual, u'nopqrstuvwxyz') def test_hierarchical_partials_with_lambdas(self): view = Lambdas() @@ -164,8 +164,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): # Check the loader attribute. loader = custom.loader - self.assertEquals(loader.extension, 'mustache') - self.assertEquals(loader.file_encoding, sys.getdefaultencoding()) + self.assertEqual(loader.extension, 'mustache') + self.assertEqual(loader.file_encoding, sys.getdefaultencoding()) # TODO: finish testing the other Loader attributes. to_unicode = loader.to_unicode @@ -255,8 +255,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): # Check that our unicode() above was called. self._assert_template(custom_loader, view, u'foo') - self.assertEquals(loader.s, "template-foo") - self.assertEquals(loader.encoding, "encoding-foo") + self.assertEqual(loader.s, "template-foo") + self.assertEqual(loader.encoding, "encoding-foo") # TODO: migrate these tests into the SpecLoaderTests class. @@ -273,7 +273,7 @@ class TemplateSpecTests(unittest.TestCase): def _assert_template_location(self, view, expected): locator = self._make_locator() actual = locator._find_relative(view) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_find_relative(self): """ @@ -343,7 +343,7 @@ class TemplateSpecTests(unittest.TestCase): actual = locator._find(view) expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_find__without_directory(self): """ @@ -358,14 +358,14 @@ class TemplateSpecTests(unittest.TestCase): actual = locator._find(view) expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def _assert_get_template(self, custom, expected): locator = self._make_locator() actual = locator.load(custom) - self.assertEquals(type(actual), unicode) - self.assertEquals(actual, expected) + self.assertEqual(type(actual), unicode) + self.assertEqual(actual, expected) def test_get_template(self): """ -- cgit v1.2.1 From 63d6dd4979c8c813172fb78fae30c72ecba1c2ed Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 5 Apr 2012 07:44:34 -0700 Subject: Python 3 support: use html module instead of cgi.escape(). Python 3.2 deprecates cgi.escape() and introduces the html module in its place. --- pystache/defaults.py | 10 ++++++++-- pystache/renderer.py | 8 ++++---- pystache/tests/test_renderengine.py | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) (limited to 'pystache') diff --git a/pystache/defaults.py b/pystache/defaults.py index b696410..d0be493 100644 --- a/pystache/defaults.py +++ b/pystache/defaults.py @@ -8,7 +8,12 @@ does not otherwise specify a value. """ -import cgi +try: + # Python 3.2 deprecates cgi.escape() and adds the html module as a replacement. + import html +except ImportError: + import cgi as html + import os import sys @@ -42,9 +47,10 @@ SEARCH_DIRS = [os.curdir] # i.e. ['.'] # The quote=True argument causes double quotes to be escaped, # but not single quotes: # +# http://docs.python.org/dev/library/html.html#html.escape # http://docs.python.org/library/cgi.html#cgi.escape # -TAG_ESCAPE = lambda u: cgi.escape(u, quote=True) +TAG_ESCAPE = lambda u: html.escape(u, quote=True) # The default template extension. TEMPLATE_EXTENSION = 'mustache' diff --git a/pystache/renderer.py b/pystache/renderer.py index 5bd2a3f..ef6124a 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -64,10 +64,10 @@ class Renderer(object): this class will only pass it unicode strings. The constructor assigns this function to the constructed instance's escape() method. - The argument defaults to `cgi.escape(s, quote=True)`. To - disable escaping entirely, one can pass `lambda u: u` as the - escape function, for example. One may also wish to consider - using markupsafe's escape function: markupsafe.escape(). + To disable escaping entirely, one can pass `lambda u: u` + as the escape function, for example. One may also wish to + consider using markupsafe's escape function: markupsafe.escape(). + This argument defaults to the package default. file_encoding: the name of the default encoding to use when reading template files. All templates are converted to unicode prior diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index 2d2582b..f91d2a7 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -5,10 +5,10 @@ Unit tests of renderengine.py. """ -import cgi import unittest from pystache.context import Context +from pystache import defaults from pystache.parser import ParsingError from pystache.renderengine import RenderEngine from pystache.tests.common import AssertStringMixin @@ -47,7 +47,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): Create and return a default RenderEngine for testing. """ - escape = lambda s: unicode(cgi.escape(s)) + escape = defaults.TAG_ESCAPE engine = RenderEngine(literal=unicode, escape=escape, load_partial=None) return engine -- cgit v1.2.1 From 75030e7d6cd03507a6941fe8badedd5997a7518b Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 14:22:33 -0700 Subject: Addressed TODO to rename _make_locator() to _make_loader(). --- pystache/tests/test_template_spec.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index 7c606c3..1095e14 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -266,13 +266,12 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): # TemplateSpec attributes or something). class TemplateSpecTests(unittest.TestCase): - # TODO: rename this method to _make_loader(). - def _make_locator(self): + def _make_loader(self): return SpecLoader() def _assert_template_location(self, view, expected): - locator = self._make_locator() - actual = locator._find_relative(view) + loader = self._make_loader() + actual = loader._find_relative(view) self.assertEqual(actual, expected) def test_find_relative(self): @@ -334,13 +333,13 @@ class TemplateSpecTests(unittest.TestCase): Test _find() with a view that has a directory specified. """ - locator = self._make_locator() + loader = self._make_loader() view = SampleView() view.template_rel_path = 'foo/bar.txt' - self.assertTrue(locator._find_relative(view)[0] is not None) + self.assertTrue(loader._find_relative(view)[0] is not None) - actual = locator._find(view) + actual = loader._find(view) expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) self.assertEqual(actual, expected) @@ -350,19 +349,19 @@ class TemplateSpecTests(unittest.TestCase): Test _find() with a view that doesn't have a directory specified. """ - locator = self._make_locator() + loader = self._make_loader() view = SampleView() - self.assertTrue(locator._find_relative(view)[0] is None) + self.assertTrue(loader._find_relative(view)[0] is None) - actual = locator._find(view) + actual = loader._find(view) expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) self.assertEqual(actual, expected) def _assert_get_template(self, custom, expected): - locator = self._make_locator() - actual = locator.load(custom) + loader = self._make_loader() + actual = loader.load(custom) self.assertEqual(type(actual), unicode) self.assertEqual(actual, expected) -- cgit v1.2.1 From ba04172d95eb7bef2e4935a2057622edba8e9509 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 14:37:56 -0700 Subject: Added module pystache.common with a read(path) function. --- pystache/common.py | 26 ++++++++++++++++++++++++++ pystache/loader.py | 10 +++------- 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 pystache/common.py (limited to 'pystache') diff --git a/pystache/common.py b/pystache/common.py new file mode 100644 index 0000000..00f8a77 --- /dev/null +++ b/pystache/common.py @@ -0,0 +1,26 @@ +# coding: utf-8 + +""" +Exposes common functions. + +""" + +# This function was designed to be portable across Python versions -- both +# with older versions and with Python 3 after applying 2to3. +def read(path): + """ + Return the contents of a text file as a byte string. + + """ + # Opening in binary mode is necessary for compatibility across Python + # 2 and 3. In both Python 2 and 3, open() defaults to opening files in + # text mode. However, in Python 2, open() returns file objects whose + # read() method returns byte strings (strings of type `str` in Python 2), + # whereas in Python 3, the file object returns unicode strings (strings + # of type `str` in Python 3). + f = open(path, 'rb') + # We avoid use of the with keyword for Python 2.4 support. + try: + return f.read() + finally: + f.close() diff --git a/pystache/loader.py b/pystache/loader.py index 3506acf..3a7d02c 100644 --- a/pystache/loader.py +++ b/pystache/loader.py @@ -8,6 +8,7 @@ This module provides a Loader class for locating and reading templates. import os import sys +from pystache import common from pystache import defaults from pystache.locator import Locator @@ -106,17 +107,12 @@ class Loader(object): Read the template at the given path, and return it as a unicode string. """ - # We avoid use of the with keyword for Python 2.4 support. - f = open(path, 'r') - try: - text = f.read() - finally: - f.close() + b = common.read(path) if encoding is None: encoding = self.file_encoding - return self.unicode(text, encoding) + return self.unicode(b, encoding) # TODO: unit-test this method. def load_name(self, name): -- cgit v1.2.1 From a54f8540ad87a00331416e946da06581e5d51a98 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 14:46:01 -0700 Subject: Fixed a test case in Python 3 related to the system's default encoding. --- pystache/tests/test_template_spec.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index 1095e14..daa3eb1 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -267,7 +267,11 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): class TemplateSpecTests(unittest.TestCase): def _make_loader(self): - return SpecLoader() + # Python 2 and 3 have different default encodings, so we need + # to specify the encoding explicitly to have consistent test + # results across both versions. + loader = Loader(file_encoding='ascii') + return SpecLoader(loader=loader) def _assert_template_location(self, view, expected): loader = self._make_loader() -- cgit v1.2.1 From 96f6c13c5befc76aa746e2ec14318a64fee63db0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 14:49:08 -0700 Subject: Added three TODO's to test_template_spec.py. --- pystache/tests/test_template_spec.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'pystache') diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index daa3eb1..f6d11fc 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -217,6 +217,9 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): custom = TemplateSpec() custom.template = u'é'.encode('utf-8') + # TODO: share code with other code that instantiates a SpecLoader. + # TODO: add test_spec_loader.py. + # TODO: rename spec_loader.py to specloader.py. self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') custom.template_encoding = 'utf-8' -- cgit v1.2.1 From 64878124a00bf93163a3e95c244c2d5c79f37eaf Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 15:20:23 -0700 Subject: Renamed spec_loader module to specloader. --- pystache/renderer.py | 2 +- pystache/spec_loader.py | 87 ------------------------------------ pystache/specloader.py | 87 ++++++++++++++++++++++++++++++++++++ pystache/tests/test_template_spec.py | 3 +- 4 files changed, 89 insertions(+), 90 deletions(-) delete mode 100644 pystache/spec_loader.py create mode 100644 pystache/specloader.py (limited to 'pystache') diff --git a/pystache/renderer.py b/pystache/renderer.py index ef6124a..3356ec1 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -9,7 +9,7 @@ from pystache import defaults from pystache.context import Context from pystache.loader import Loader from pystache.renderengine import RenderEngine -from pystache.spec_loader import SpecLoader +from pystache.specloader import SpecLoader from pystache.template_spec import TemplateSpec diff --git a/pystache/spec_loader.py b/pystache/spec_loader.py deleted file mode 100644 index 3cb0f1a..0000000 --- a/pystache/spec_loader.py +++ /dev/null @@ -1,87 +0,0 @@ -# coding: utf-8 - -""" -This module supports customized (aka special or specified) template loading. - -""" - -import os.path - -from pystache.loader import Loader - - -# TODO: add test cases for this class. -class SpecLoader(object): - - """ - Supports loading custom-specified templates (from TemplateSpec instances). - - """ - - def __init__(self, loader=None): - if loader is None: - loader = Loader() - - self.loader = loader - - def _find_relative(self, spec): - """ - Return the path to the template as a relative (dir, file_name) pair. - - The directory returned is relative to the directory containing the - class definition of the given object. The method returns None for - this directory if the directory is unknown without first searching - the search directories. - - """ - if spec.template_rel_path is not None: - return os.path.split(spec.template_rel_path) - # Otherwise, determine the file name separately. - - locator = self.loader._make_locator() - - # We do not use the ternary operator for Python 2.4 support. - if spec.template_name is not None: - template_name = spec.template_name - else: - template_name = locator.make_template_name(spec) - - file_name = locator.make_file_name(template_name, spec.template_extension) - - return (spec.template_rel_directory, file_name) - - def _find(self, spec): - """ - Find and return the path to the template associated to the instance. - - """ - dir_path, file_name = self._find_relative(spec) - - locator = self.loader._make_locator() - - if dir_path is None: - # Then we need to search for the path. - path = locator.find_object(spec, self.loader.search_dirs, file_name=file_name) - else: - obj_dir = locator.get_object_directory(spec) - path = os.path.join(obj_dir, dir_path, file_name) - - return path - - def load(self, spec): - """ - Find and return the template associated to a TemplateSpec instance. - - Returns the template as a unicode string. - - Arguments: - - spec: a TemplateSpec instance. - - """ - if spec.template is not None: - return self.loader.unicode(spec.template, spec.template_encoding) - - path = self._find(spec) - - return self.loader.read(path, spec.template_encoding) diff --git a/pystache/specloader.py b/pystache/specloader.py new file mode 100644 index 0000000..3cb0f1a --- /dev/null +++ b/pystache/specloader.py @@ -0,0 +1,87 @@ +# coding: utf-8 + +""" +This module supports customized (aka special or specified) template loading. + +""" + +import os.path + +from pystache.loader import Loader + + +# TODO: add test cases for this class. +class SpecLoader(object): + + """ + Supports loading custom-specified templates (from TemplateSpec instances). + + """ + + def __init__(self, loader=None): + if loader is None: + loader = Loader() + + self.loader = loader + + def _find_relative(self, spec): + """ + Return the path to the template as a relative (dir, file_name) pair. + + The directory returned is relative to the directory containing the + class definition of the given object. The method returns None for + this directory if the directory is unknown without first searching + the search directories. + + """ + if spec.template_rel_path is not None: + return os.path.split(spec.template_rel_path) + # Otherwise, determine the file name separately. + + locator = self.loader._make_locator() + + # We do not use the ternary operator for Python 2.4 support. + if spec.template_name is not None: + template_name = spec.template_name + else: + template_name = locator.make_template_name(spec) + + file_name = locator.make_file_name(template_name, spec.template_extension) + + return (spec.template_rel_directory, file_name) + + def _find(self, spec): + """ + Find and return the path to the template associated to the instance. + + """ + dir_path, file_name = self._find_relative(spec) + + locator = self.loader._make_locator() + + if dir_path is None: + # Then we need to search for the path. + path = locator.find_object(spec, self.loader.search_dirs, file_name=file_name) + else: + obj_dir = locator.get_object_directory(spec) + path = os.path.join(obj_dir, dir_path, file_name) + + return path + + def load(self, spec): + """ + Find and return the template associated to a TemplateSpec instance. + + Returns the template as a unicode string. + + Arguments: + + spec: a TemplateSpec instance. + + """ + if spec.template is not None: + return self.loader.unicode(spec.template, spec.template_encoding) + + path = self._find(spec) + + return self.loader.read(path, spec.template_encoding) diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py index f6d11fc..145a4e8 100644 --- a/pystache/tests/test_template_spec.py +++ b/pystache/tests/test_template_spec.py @@ -18,7 +18,7 @@ from pystache import Renderer from pystache import TemplateSpec from pystache.locator import Locator from pystache.loader import Loader -from pystache.spec_loader import SpecLoader +from pystache.specloader import SpecLoader from pystache.tests.common import DATA_DIR from pystache.tests.common import EXAMPLES_DIR from pystache.tests.common import AssertIsMixin @@ -219,7 +219,6 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): # TODO: share code with other code that instantiates a SpecLoader. # TODO: add test_spec_loader.py. - # TODO: rename spec_loader.py to specloader.py. self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') custom.template_encoding = 'utf-8' -- cgit v1.2.1 From fd8434d09cb842ef88fde8b63bc44adb6e3d20c0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 15:23:33 -0700 Subject: Renamed two test modules: test_template_spec, test_spec -> test_specloader, test_mustachespec. --- pystache/tests/test_mustachespec.py | 202 ++++++++++++++++++ pystache/tests/test_spec.py | 202 ------------------ pystache/tests/test_specloader.py | 394 +++++++++++++++++++++++++++++++++++ pystache/tests/test_template_spec.py | 394 ----------------------------------- 4 files changed, 596 insertions(+), 596 deletions(-) create mode 100644 pystache/tests/test_mustachespec.py delete mode 100644 pystache/tests/test_spec.py create mode 100644 pystache/tests/test_specloader.py delete mode 100644 pystache/tests/test_template_spec.py (limited to 'pystache') diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py new file mode 100644 index 0000000..06e4dd8 --- /dev/null +++ b/pystache/tests/test_mustachespec.py @@ -0,0 +1,202 @@ +# coding: utf-8 + +""" +Creates a unittest.TestCase for the tests defined in the mustache spec. + +""" + +# TODO: this module can be cleaned up somewhat. + +FILE_ENCODING = 'utf-8' # the encoding of the spec test files. + + +try: + # We try yaml first since it is more convenient when adding and modifying + # test cases by hand (since the YAML is human-readable and is the master + # from which the JSON format is generated). + import yaml +except ImportError: + try: + import json + except: + # The module json is not available prior to Python 2.6, whereas + # simplejson is. The simplejson package dropped support for Python 2.4 + # in simplejson v2.1.0, so Python 2.4 requires a simplejson install + # older than the most recent version. + import simplejson as json + file_extension = 'json' + parser = json +else: + file_extension = 'yml' + parser = yaml + + +import codecs +import glob +import os.path +import unittest + +import pystache +from pystache.renderer import Renderer +from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR + + +spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) + + +def parse(u, file_extension): + """ + Arguments: + + u: a unicode string. + + """ + # TODO: find a cleaner mechanism for choosing between the two. + if file_extension[0] == 'j': + # Then json. + + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # + return json.loads(u) + # Otherwise, yaml. + + def code_constructor(loader, node): + value = loader.construct_mapping(node) + return eval(value['python'], {}) + + yaml.add_constructor(u'!code', code_constructor) + return yaml.load(u) + + +# This test case lets us alert the user that spec tests are missing. +class CheckSpecTestsFound(unittest.TestCase): + + def test_spec_tests_found(self): + if len(spec_paths) > 0: + return + raise Exception("Spec tests not found in: %s\n " + "Consult the README file on how to add the Mustache spec tests." % repr(SPEC_TEST_DIR)) + + +# TODO: give this a name better than MustacheSpec. +class MustacheSpec(unittest.TestCase, AssertStringMixin): + pass + + +def buildTest(testData, spec_filename, parser): + """ + Arguments: + + parser: the module used for parsing (e.g. yaml or json). + + """ + + name = testData['name'] + description = testData['desc'] + + test_name = "%s (%s)" % (name, spec_filename) + + def test(self): + template = testData['template'] + partials = testData.has_key('partials') and testData['partials'] or {} + # PyYAML seems to leave ASCII strings as byte strings. + expected = unicode(testData['expected']) + data = testData['data'] + + # Convert code strings to functions. + # TODO: make this section of code easier to understand. + new_data = {} + for key, val in data.iteritems(): + if isinstance(val, dict) and val.get('__tag__') == 'code': + val = eval(val['python']) + new_data[key] = val + + renderer = Renderer(partials=partials) + actual = renderer.render(template, new_data) + + # We need to escape the strings that occur in our format string because + # they can contain % symbols, for example (in delimiters.yml)-- + # + # "template: '{{=<% %>=}}(<%text%>)'" + # + def escape(s): + return s.replace("%", "%%") + + subs = [description, template, parser.__version__, str(parser)] + subs = tuple([escape(sub) for sub in subs]) + # We include the parsing module version info to help with troubleshooting + # yaml/json/simplejson issues. + message = """%s + + Template: \"""%s\""" + + %%s + + (using version %s of %s) + """ % subs + + self.assertString(actual, expected, format=message) + + # The name must begin with "test" for nosetests test discovery to work. + name = 'test: "%s"' % test_name + + # If we don't convert unicode to str, we get the following error: + # "TypeError: __name__ must be set to a string object" + test.__name__ = str(name) + + return test + + +for spec_path in spec_paths: + + file_name = os.path.basename(spec_path) + + # We use codecs.open() for pre Python 2.6 support and because it ports + # correctly to Python 3: + # + # "If pre-2.6 compatibility is needed, then you should use codecs.open() + # instead. This will make sure that you get back unicode strings in Python 2." + # + # (from http://docs.python.org/py3k/howto/pyporting.html#text-files ) + # + # TODO: share code here with pystache's open() code. + f = codecs.open(spec_path, 'r', encoding=FILE_ENCODING) + try: + u = f.read() + finally: + f.close() + + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # + spec_data = parse(u, file_extension) + + tests = spec_data['tests'] + + for test in tests: + test = buildTest(test, file_name, parser) + setattr(MustacheSpec, test.__name__, test) + # Prevent this variable from being interpreted as another test. + del(test) + +if __name__ == '__main__': + unittest.main() diff --git a/pystache/tests/test_spec.py b/pystache/tests/test_spec.py deleted file mode 100644 index 06e4dd8..0000000 --- a/pystache/tests/test_spec.py +++ /dev/null @@ -1,202 +0,0 @@ -# coding: utf-8 - -""" -Creates a unittest.TestCase for the tests defined in the mustache spec. - -""" - -# TODO: this module can be cleaned up somewhat. - -FILE_ENCODING = 'utf-8' # the encoding of the spec test files. - - -try: - # We try yaml first since it is more convenient when adding and modifying - # test cases by hand (since the YAML is human-readable and is the master - # from which the JSON format is generated). - import yaml -except ImportError: - try: - import json - except: - # The module json is not available prior to Python 2.6, whereas - # simplejson is. The simplejson package dropped support for Python 2.4 - # in simplejson v2.1.0, so Python 2.4 requires a simplejson install - # older than the most recent version. - import simplejson as json - file_extension = 'json' - parser = json -else: - file_extension = 'yml' - parser = yaml - - -import codecs -import glob -import os.path -import unittest - -import pystache -from pystache.renderer import Renderer -from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR - - -spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) - - -def parse(u, file_extension): - """ - Arguments: - - u: a unicode string. - - """ - # TODO: find a cleaner mechanism for choosing between the two. - if file_extension[0] == 'j': - # Then json. - - # The only way to get the simplejson module to return unicode strings - # is to pass it unicode. See, for example-- - # - # http://code.google.com/p/simplejson/issues/detail?id=40 - # - # and the documentation of simplejson.loads(): - # - # "If s is a str then decoded JSON strings that contain only ASCII - # characters may be parsed as str for performance and memory reasons. - # If your code expects only unicode the appropriate solution is - # decode s to unicode prior to calling loads." - # - return json.loads(u) - # Otherwise, yaml. - - def code_constructor(loader, node): - value = loader.construct_mapping(node) - return eval(value['python'], {}) - - yaml.add_constructor(u'!code', code_constructor) - return yaml.load(u) - - -# This test case lets us alert the user that spec tests are missing. -class CheckSpecTestsFound(unittest.TestCase): - - def test_spec_tests_found(self): - if len(spec_paths) > 0: - return - raise Exception("Spec tests not found in: %s\n " - "Consult the README file on how to add the Mustache spec tests." % repr(SPEC_TEST_DIR)) - - -# TODO: give this a name better than MustacheSpec. -class MustacheSpec(unittest.TestCase, AssertStringMixin): - pass - - -def buildTest(testData, spec_filename, parser): - """ - Arguments: - - parser: the module used for parsing (e.g. yaml or json). - - """ - - name = testData['name'] - description = testData['desc'] - - test_name = "%s (%s)" % (name, spec_filename) - - def test(self): - template = testData['template'] - partials = testData.has_key('partials') and testData['partials'] or {} - # PyYAML seems to leave ASCII strings as byte strings. - expected = unicode(testData['expected']) - data = testData['data'] - - # Convert code strings to functions. - # TODO: make this section of code easier to understand. - new_data = {} - for key, val in data.iteritems(): - if isinstance(val, dict) and val.get('__tag__') == 'code': - val = eval(val['python']) - new_data[key] = val - - renderer = Renderer(partials=partials) - actual = renderer.render(template, new_data) - - # We need to escape the strings that occur in our format string because - # they can contain % symbols, for example (in delimiters.yml)-- - # - # "template: '{{=<% %>=}}(<%text%>)'" - # - def escape(s): - return s.replace("%", "%%") - - subs = [description, template, parser.__version__, str(parser)] - subs = tuple([escape(sub) for sub in subs]) - # We include the parsing module version info to help with troubleshooting - # yaml/json/simplejson issues. - message = """%s - - Template: \"""%s\""" - - %%s - - (using version %s of %s) - """ % subs - - self.assertString(actual, expected, format=message) - - # The name must begin with "test" for nosetests test discovery to work. - name = 'test: "%s"' % test_name - - # If we don't convert unicode to str, we get the following error: - # "TypeError: __name__ must be set to a string object" - test.__name__ = str(name) - - return test - - -for spec_path in spec_paths: - - file_name = os.path.basename(spec_path) - - # We use codecs.open() for pre Python 2.6 support and because it ports - # correctly to Python 3: - # - # "If pre-2.6 compatibility is needed, then you should use codecs.open() - # instead. This will make sure that you get back unicode strings in Python 2." - # - # (from http://docs.python.org/py3k/howto/pyporting.html#text-files ) - # - # TODO: share code here with pystache's open() code. - f = codecs.open(spec_path, 'r', encoding=FILE_ENCODING) - try: - u = f.read() - finally: - f.close() - - # The only way to get the simplejson module to return unicode strings - # is to pass it unicode. See, for example-- - # - # http://code.google.com/p/simplejson/issues/detail?id=40 - # - # and the documentation of simplejson.loads(): - # - # "If s is a str then decoded JSON strings that contain only ASCII - # characters may be parsed as str for performance and memory reasons. - # If your code expects only unicode the appropriate solution is - # decode s to unicode prior to calling loads." - # - spec_data = parse(u, file_extension) - - tests = spec_data['tests'] - - for test in tests: - test = buildTest(test, file_name, parser) - setattr(MustacheSpec, test.__name__, test) - # Prevent this variable from being interpreted as another test. - del(test) - -if __name__ == '__main__': - unittest.main() diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py new file mode 100644 index 0000000..145a4e8 --- /dev/null +++ b/pystache/tests/test_specloader.py @@ -0,0 +1,394 @@ +# coding: utf-8 + +""" +Unit tests for template_spec.py. + +""" + +import os.path +import sys +import unittest + +import examples +from examples.simple import Simple +from examples.complex import Complex +from examples.lambdas import Lambdas +from examples.inverted import Inverted, InvertedLists +from pystache import Renderer +from pystache import TemplateSpec +from pystache.locator import Locator +from pystache.loader import Loader +from pystache.specloader import SpecLoader +from pystache.tests.common import DATA_DIR +from pystache.tests.common import EXAMPLES_DIR +from pystache.tests.common import AssertIsMixin +from pystache.tests.common import AssertStringMixin +from pystache.tests.data.views import SampleView +from pystache.tests.data.views import NonAscii + + +class Thing(object): + pass + + +class ViewTestCase(unittest.TestCase, AssertStringMixin): + + def test_template_rel_directory(self): + """ + Test that View.template_rel_directory is respected. + + """ + class Tagless(TemplateSpec): + pass + + view = Tagless() + renderer = Renderer() + + self.assertRaises(IOError, renderer.render, view) + + # TODO: change this test to remove the following brittle line. + view.template_rel_directory = "../../examples" + actual = renderer.render(view) + self.assertEqual(actual, "No tags...") + + def test_template_path_for_partials(self): + """ + Test that View.template_rel_path is respected for partials. + + """ + spec = TemplateSpec() + spec.template = "Partial: {{>tagless}}" + + renderer1 = Renderer() + renderer2 = Renderer(search_dirs=EXAMPLES_DIR) + + self.assertRaises(IOError, renderer1.render, spec) + + actual = renderer2.render(spec) + self.assertEqual(actual, "Partial: No tags...") + + def test_basic_method_calls(self): + renderer = Renderer() + actual = renderer.render(Simple()) + + self.assertString(actual, u"Hi pizza!") + + def test_non_callable_attributes(self): + view = Simple() + view.thing = 'Chris' + + renderer = Renderer() + actual = renderer.render(view) + self.assertEqual(actual, "Hi Chris!") + + def test_complex(self): + renderer = Renderer() + actual = renderer.render(Complex()) + self.assertString(actual, u"""\ +

Colors

+""") + + def test_higher_order_replace(self): + renderer = Renderer() + actual = renderer.render(Lambdas()) + self.assertEqual(actual, 'bar != bar. oh, it does!') + + def test_higher_order_rot13(self): + view = Lambdas() + view.template = '{{#rot13}}abcdefghijklm{{/rot13}}' + + renderer = Renderer() + actual = renderer.render(view) + self.assertString(actual, u'nopqrstuvwxyz') + + def test_higher_order_lambda(self): + view = Lambdas() + view.template = '{{#sort}}zyxwvutsrqponmlkjihgfedcba{{/sort}}' + + renderer = Renderer() + actual = renderer.render(view) + self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz') + + def test_partials_with_lambda(self): + view = Lambdas() + view.template = '{{>partial_with_lambda}}' + + renderer = Renderer(search_dirs=EXAMPLES_DIR) + actual = renderer.render(view) + self.assertEqual(actual, u'nopqrstuvwxyz') + + def test_hierarchical_partials_with_lambdas(self): + view = Lambdas() + view.template = '{{>partial_with_partial_and_lambda}}' + + renderer = Renderer(search_dirs=EXAMPLES_DIR) + actual = renderer.render(view) + self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz') + + def test_inverted(self): + renderer = Renderer() + actual = renderer.render(Inverted()) + self.assertString(actual, u"""one, two, three, empty list""") + + def test_accessing_properties_on_parent_object_from_child_objects(self): + parent = Thing() + parent.this = 'derp' + parent.children = [Thing()] + view = Simple() + view.template = "{{#parent}}{{#children}}{{this}}{{/children}}{{/parent}}" + + renderer = Renderer() + actual = renderer.render(view, {'parent': parent}) + + self.assertString(actual, u'derp') + + def test_inverted_lists(self): + renderer = Renderer() + actual = renderer.render(InvertedLists()) + self.assertString(actual, u"""one, two, three, empty list""") + + +class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): + + """ + Tests template_spec.SpecLoader. + + """ + + def test_init__defaults(self): + custom = SpecLoader() + + # Check the loader attribute. + loader = custom.loader + self.assertEqual(loader.extension, 'mustache') + self.assertEqual(loader.file_encoding, sys.getdefaultencoding()) + # TODO: finish testing the other Loader attributes. + to_unicode = loader.to_unicode + + def test_init__loader(self): + loader = Loader() + custom = SpecLoader(loader=loader) + + self.assertIs(custom.loader, loader) + + # TODO: rename to something like _assert_load(). + def _assert_template(self, loader, custom, expected): + self.assertString(loader.load(custom), expected) + + def test_load__template__type_str(self): + """ + Test the template attribute: str string. + + """ + custom = TemplateSpec() + custom.template = "abc" + + self._assert_template(SpecLoader(), custom, u"abc") + + def test_load__template__type_unicode(self): + """ + Test the template attribute: unicode string. + + """ + custom = TemplateSpec() + custom.template = u"abc" + + self._assert_template(SpecLoader(), custom, u"abc") + + def test_load__template__unicode_non_ascii(self): + """ + Test the template attribute: non-ascii unicode string. + + """ + custom = TemplateSpec() + custom.template = u"é" + + self._assert_template(SpecLoader(), custom, u"é") + + def test_load__template__with_template_encoding(self): + """ + Test the template attribute: with template encoding attribute. + + """ + custom = TemplateSpec() + custom.template = u'é'.encode('utf-8') + + # TODO: share code with other code that instantiates a SpecLoader. + # TODO: add test_spec_loader.py. + self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') + + custom.template_encoding = 'utf-8' + self._assert_template(SpecLoader(), custom, u'é') + + # TODO: make this test complete. + def test_load__template__correct_loader(self): + """ + Test that reader.unicode() is called correctly. + + This test tests that the correct reader is called with the correct + arguments. This is a catch-all test to supplement the other + test cases. It tests SpecLoader.load() independent of reader.unicode() + being implemented correctly (and tested). + + """ + class MockLoader(Loader): + + def __init__(self): + self.s = None + self.encoding = None + + # Overrides the existing method. + def unicode(self, s, encoding=None): + self.s = s + self.encoding = encoding + return u"foo" + + loader = MockLoader() + custom_loader = SpecLoader() + custom_loader.loader = loader + + view = TemplateSpec() + view.template = "template-foo" + view.template_encoding = "encoding-foo" + + # Check that our unicode() above was called. + self._assert_template(custom_loader, view, u'foo') + self.assertEqual(loader.s, "template-foo") + self.assertEqual(loader.encoding, "encoding-foo") + + +# TODO: migrate these tests into the SpecLoaderTests class. +# TODO: rename the get_template() tests to test load(). +# TODO: condense, reorganize, and rename the tests so that it is +# clear whether we have full test coverage (e.g. organized by +# TemplateSpec attributes or something). +class TemplateSpecTests(unittest.TestCase): + + def _make_loader(self): + # Python 2 and 3 have different default encodings, so we need + # to specify the encoding explicitly to have consistent test + # results across both versions. + loader = Loader(file_encoding='ascii') + return SpecLoader(loader=loader) + + def _assert_template_location(self, view, expected): + loader = self._make_loader() + actual = loader._find_relative(view) + self.assertEqual(actual, expected) + + def test_find_relative(self): + """ + Test _find_relative(): default behavior (no attributes set). + + """ + view = SampleView() + self._assert_template_location(view, (None, 'sample_view.mustache')) + + def test_find_relative__template_rel_path__file_name_only(self): + """ + Test _find_relative(): template_rel_path attribute. + + """ + view = SampleView() + view.template_rel_path = 'template.txt' + self._assert_template_location(view, ('', 'template.txt')) + + def test_find_relative__template_rel_path__file_name_with_directory(self): + """ + Test _find_relative(): template_rel_path attribute. + + """ + view = SampleView() + view.template_rel_path = 'foo/bar/template.txt' + self._assert_template_location(view, ('foo/bar', 'template.txt')) + + def test_find_relative__template_rel_directory(self): + """ + Test _find_relative(): template_rel_directory attribute. + + """ + view = SampleView() + view.template_rel_directory = 'foo' + + self._assert_template_location(view, ('foo', 'sample_view.mustache')) + + def test_find_relative__template_name(self): + """ + Test _find_relative(): template_name attribute. + + """ + view = SampleView() + view.template_name = 'new_name' + self._assert_template_location(view, (None, 'new_name.mustache')) + + def test_find_relative__template_extension(self): + """ + Test _find_relative(): template_extension attribute. + + """ + view = SampleView() + view.template_extension = 'txt' + self._assert_template_location(view, (None, 'sample_view.txt')) + + def test_find__with_directory(self): + """ + Test _find() with a view that has a directory specified. + + """ + loader = self._make_loader() + + view = SampleView() + view.template_rel_path = 'foo/bar.txt' + self.assertTrue(loader._find_relative(view)[0] is not None) + + actual = loader._find(view) + expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) + + self.assertEqual(actual, expected) + + def test_find__without_directory(self): + """ + Test _find() with a view that doesn't have a directory specified. + + """ + loader = self._make_loader() + + view = SampleView() + self.assertTrue(loader._find_relative(view)[0] is None) + + actual = loader._find(view) + expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) + + self.assertEqual(actual, expected) + + def _assert_get_template(self, custom, expected): + loader = self._make_loader() + actual = loader.load(custom) + + self.assertEqual(type(actual), unicode) + self.assertEqual(actual, expected) + + def test_get_template(self): + """ + Test get_template(): default behavior (no attributes set). + + """ + view = SampleView() + + self._assert_get_template(view, u"ascii: abc") + + def test_get_template__template_encoding(self): + """ + Test get_template(): template_encoding attribute. + + """ + view = NonAscii() + + self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo') + + view.template_encoding = 'utf-8' + self._assert_get_template(view, u"non-ascii: é") diff --git a/pystache/tests/test_template_spec.py b/pystache/tests/test_template_spec.py deleted file mode 100644 index 145a4e8..0000000 --- a/pystache/tests/test_template_spec.py +++ /dev/null @@ -1,394 +0,0 @@ -# coding: utf-8 - -""" -Unit tests for template_spec.py. - -""" - -import os.path -import sys -import unittest - -import examples -from examples.simple import Simple -from examples.complex import Complex -from examples.lambdas import Lambdas -from examples.inverted import Inverted, InvertedLists -from pystache import Renderer -from pystache import TemplateSpec -from pystache.locator import Locator -from pystache.loader import Loader -from pystache.specloader import SpecLoader -from pystache.tests.common import DATA_DIR -from pystache.tests.common import EXAMPLES_DIR -from pystache.tests.common import AssertIsMixin -from pystache.tests.common import AssertStringMixin -from pystache.tests.data.views import SampleView -from pystache.tests.data.views import NonAscii - - -class Thing(object): - pass - - -class ViewTestCase(unittest.TestCase, AssertStringMixin): - - def test_template_rel_directory(self): - """ - Test that View.template_rel_directory is respected. - - """ - class Tagless(TemplateSpec): - pass - - view = Tagless() - renderer = Renderer() - - self.assertRaises(IOError, renderer.render, view) - - # TODO: change this test to remove the following brittle line. - view.template_rel_directory = "../../examples" - actual = renderer.render(view) - self.assertEqual(actual, "No tags...") - - def test_template_path_for_partials(self): - """ - Test that View.template_rel_path is respected for partials. - - """ - spec = TemplateSpec() - spec.template = "Partial: {{>tagless}}" - - renderer1 = Renderer() - renderer2 = Renderer(search_dirs=EXAMPLES_DIR) - - self.assertRaises(IOError, renderer1.render, spec) - - actual = renderer2.render(spec) - self.assertEqual(actual, "Partial: No tags...") - - def test_basic_method_calls(self): - renderer = Renderer() - actual = renderer.render(Simple()) - - self.assertString(actual, u"Hi pizza!") - - def test_non_callable_attributes(self): - view = Simple() - view.thing = 'Chris' - - renderer = Renderer() - actual = renderer.render(view) - self.assertEqual(actual, "Hi Chris!") - - def test_complex(self): - renderer = Renderer() - actual = renderer.render(Complex()) - self.assertString(actual, u"""\ -

Colors

-""") - - def test_higher_order_replace(self): - renderer = Renderer() - actual = renderer.render(Lambdas()) - self.assertEqual(actual, 'bar != bar. oh, it does!') - - def test_higher_order_rot13(self): - view = Lambdas() - view.template = '{{#rot13}}abcdefghijklm{{/rot13}}' - - renderer = Renderer() - actual = renderer.render(view) - self.assertString(actual, u'nopqrstuvwxyz') - - def test_higher_order_lambda(self): - view = Lambdas() - view.template = '{{#sort}}zyxwvutsrqponmlkjihgfedcba{{/sort}}' - - renderer = Renderer() - actual = renderer.render(view) - self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz') - - def test_partials_with_lambda(self): - view = Lambdas() - view.template = '{{>partial_with_lambda}}' - - renderer = Renderer(search_dirs=EXAMPLES_DIR) - actual = renderer.render(view) - self.assertEqual(actual, u'nopqrstuvwxyz') - - def test_hierarchical_partials_with_lambdas(self): - view = Lambdas() - view.template = '{{>partial_with_partial_and_lambda}}' - - renderer = Renderer(search_dirs=EXAMPLES_DIR) - actual = renderer.render(view) - self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz') - - def test_inverted(self): - renderer = Renderer() - actual = renderer.render(Inverted()) - self.assertString(actual, u"""one, two, three, empty list""") - - def test_accessing_properties_on_parent_object_from_child_objects(self): - parent = Thing() - parent.this = 'derp' - parent.children = [Thing()] - view = Simple() - view.template = "{{#parent}}{{#children}}{{this}}{{/children}}{{/parent}}" - - renderer = Renderer() - actual = renderer.render(view, {'parent': parent}) - - self.assertString(actual, u'derp') - - def test_inverted_lists(self): - renderer = Renderer() - actual = renderer.render(InvertedLists()) - self.assertString(actual, u"""one, two, three, empty list""") - - -class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): - - """ - Tests template_spec.SpecLoader. - - """ - - def test_init__defaults(self): - custom = SpecLoader() - - # Check the loader attribute. - loader = custom.loader - self.assertEqual(loader.extension, 'mustache') - self.assertEqual(loader.file_encoding, sys.getdefaultencoding()) - # TODO: finish testing the other Loader attributes. - to_unicode = loader.to_unicode - - def test_init__loader(self): - loader = Loader() - custom = SpecLoader(loader=loader) - - self.assertIs(custom.loader, loader) - - # TODO: rename to something like _assert_load(). - def _assert_template(self, loader, custom, expected): - self.assertString(loader.load(custom), expected) - - def test_load__template__type_str(self): - """ - Test the template attribute: str string. - - """ - custom = TemplateSpec() - custom.template = "abc" - - self._assert_template(SpecLoader(), custom, u"abc") - - def test_load__template__type_unicode(self): - """ - Test the template attribute: unicode string. - - """ - custom = TemplateSpec() - custom.template = u"abc" - - self._assert_template(SpecLoader(), custom, u"abc") - - def test_load__template__unicode_non_ascii(self): - """ - Test the template attribute: non-ascii unicode string. - - """ - custom = TemplateSpec() - custom.template = u"é" - - self._assert_template(SpecLoader(), custom, u"é") - - def test_load__template__with_template_encoding(self): - """ - Test the template attribute: with template encoding attribute. - - """ - custom = TemplateSpec() - custom.template = u'é'.encode('utf-8') - - # TODO: share code with other code that instantiates a SpecLoader. - # TODO: add test_spec_loader.py. - self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') - - custom.template_encoding = 'utf-8' - self._assert_template(SpecLoader(), custom, u'é') - - # TODO: make this test complete. - def test_load__template__correct_loader(self): - """ - Test that reader.unicode() is called correctly. - - This test tests that the correct reader is called with the correct - arguments. This is a catch-all test to supplement the other - test cases. It tests SpecLoader.load() independent of reader.unicode() - being implemented correctly (and tested). - - """ - class MockLoader(Loader): - - def __init__(self): - self.s = None - self.encoding = None - - # Overrides the existing method. - def unicode(self, s, encoding=None): - self.s = s - self.encoding = encoding - return u"foo" - - loader = MockLoader() - custom_loader = SpecLoader() - custom_loader.loader = loader - - view = TemplateSpec() - view.template = "template-foo" - view.template_encoding = "encoding-foo" - - # Check that our unicode() above was called. - self._assert_template(custom_loader, view, u'foo') - self.assertEqual(loader.s, "template-foo") - self.assertEqual(loader.encoding, "encoding-foo") - - -# TODO: migrate these tests into the SpecLoaderTests class. -# TODO: rename the get_template() tests to test load(). -# TODO: condense, reorganize, and rename the tests so that it is -# clear whether we have full test coverage (e.g. organized by -# TemplateSpec attributes or something). -class TemplateSpecTests(unittest.TestCase): - - def _make_loader(self): - # Python 2 and 3 have different default encodings, so we need - # to specify the encoding explicitly to have consistent test - # results across both versions. - loader = Loader(file_encoding='ascii') - return SpecLoader(loader=loader) - - def _assert_template_location(self, view, expected): - loader = self._make_loader() - actual = loader._find_relative(view) - self.assertEqual(actual, expected) - - def test_find_relative(self): - """ - Test _find_relative(): default behavior (no attributes set). - - """ - view = SampleView() - self._assert_template_location(view, (None, 'sample_view.mustache')) - - def test_find_relative__template_rel_path__file_name_only(self): - """ - Test _find_relative(): template_rel_path attribute. - - """ - view = SampleView() - view.template_rel_path = 'template.txt' - self._assert_template_location(view, ('', 'template.txt')) - - def test_find_relative__template_rel_path__file_name_with_directory(self): - """ - Test _find_relative(): template_rel_path attribute. - - """ - view = SampleView() - view.template_rel_path = 'foo/bar/template.txt' - self._assert_template_location(view, ('foo/bar', 'template.txt')) - - def test_find_relative__template_rel_directory(self): - """ - Test _find_relative(): template_rel_directory attribute. - - """ - view = SampleView() - view.template_rel_directory = 'foo' - - self._assert_template_location(view, ('foo', 'sample_view.mustache')) - - def test_find_relative__template_name(self): - """ - Test _find_relative(): template_name attribute. - - """ - view = SampleView() - view.template_name = 'new_name' - self._assert_template_location(view, (None, 'new_name.mustache')) - - def test_find_relative__template_extension(self): - """ - Test _find_relative(): template_extension attribute. - - """ - view = SampleView() - view.template_extension = 'txt' - self._assert_template_location(view, (None, 'sample_view.txt')) - - def test_find__with_directory(self): - """ - Test _find() with a view that has a directory specified. - - """ - loader = self._make_loader() - - view = SampleView() - view.template_rel_path = 'foo/bar.txt' - self.assertTrue(loader._find_relative(view)[0] is not None) - - actual = loader._find(view) - expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) - - self.assertEqual(actual, expected) - - def test_find__without_directory(self): - """ - Test _find() with a view that doesn't have a directory specified. - - """ - loader = self._make_loader() - - view = SampleView() - self.assertTrue(loader._find_relative(view)[0] is None) - - actual = loader._find(view) - expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) - - self.assertEqual(actual, expected) - - def _assert_get_template(self, custom, expected): - loader = self._make_loader() - actual = loader.load(custom) - - self.assertEqual(type(actual), unicode) - self.assertEqual(actual, expected) - - def test_get_template(self): - """ - Test get_template(): default behavior (no attributes set). - - """ - view = SampleView() - - self._assert_get_template(view, u"ascii: abc") - - def test_get_template__template_encoding(self): - """ - Test get_template(): template_encoding attribute. - - """ - view = NonAscii() - - self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo') - - view.template_encoding = 'utf-8' - self._assert_get_template(view, u"non-ascii: é") -- cgit v1.2.1 From c53e403715a6bf9f825da91967d90a8123cba1a9 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 15:41:33 -0700 Subject: Fixed another unit test for Python 3 compatibility. --- pystache/tests/test_specloader.py | 50 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py index 145a4e8..12ec57d 100644 --- a/pystache/tests/test_specloader.py +++ b/pystache/tests/test_specloader.py @@ -152,6 +152,28 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): self.assertString(actual, u"""one, two, three, empty list""") +def _make_specloader(): + """ + Return a default SpecLoader instance for testing purposes. + + """ + # Python 2 and 3 have different default encodings. Thus, to have + # consistent test results across both versions, we need to specify + # the string and file encodings explicitly rather than relying on + # the defaults. + def to_unicode(s, encoding=None): + """ + Raises a TypeError exception if the given string is already unicode. + + """ + if encoding is None: + encoding = 'ascii' + return unicode(s, encoding, 'strict') + + loader = Loader(file_encoding='ascii', to_unicode=to_unicode) + return SpecLoader(loader=loader) + + class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): """ @@ -159,8 +181,11 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): """ + def _make_specloader(self): + return _make_specloader() + def test_init__defaults(self): - custom = SpecLoader() + custom = self._make_specloader() # Check the loader attribute. loader = custom.loader @@ -187,7 +212,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): custom = TemplateSpec() custom.template = "abc" - self._assert_template(SpecLoader(), custom, u"abc") + spec_loader = self._make_specloader() + self._assert_template(spec_loader, custom, u"abc") def test_load__template__type_unicode(self): """ @@ -197,7 +223,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): custom = TemplateSpec() custom.template = u"abc" - self._assert_template(SpecLoader(), custom, u"abc") + spec_loader = self._make_specloader() + self._assert_template(spec_loader, custom, u"abc") def test_load__template__unicode_non_ascii(self): """ @@ -207,7 +234,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): custom = TemplateSpec() custom.template = u"é" - self._assert_template(SpecLoader(), custom, u"é") + spec_loader = self._make_specloader() + self._assert_template(spec_loader, custom, u"é") def test_load__template__with_template_encoding(self): """ @@ -217,12 +245,12 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): custom = TemplateSpec() custom.template = u'é'.encode('utf-8') - # TODO: share code with other code that instantiates a SpecLoader. - # TODO: add test_spec_loader.py. - self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') + spec_loader = self._make_specloader() + + self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, u'é') custom.template_encoding = 'utf-8' - self._assert_template(SpecLoader(), custom, u'é') + self._assert_template(spec_loader, custom, u'é') # TODO: make this test complete. def test_load__template__correct_loader(self): @@ -269,11 +297,7 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): class TemplateSpecTests(unittest.TestCase): def _make_loader(self): - # Python 2 and 3 have different default encodings, so we need - # to specify the encoding explicitly to have consistent test - # results across both versions. - loader = Loader(file_encoding='ascii') - return SpecLoader(loader=loader) + return _make_specloader() def _assert_template_location(self, view, expected): loader = self._make_loader() -- cgit v1.2.1 From 46fff9a4071196722d3e6ae8b65b55c0ddc4ee5e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 15:45:27 -0700 Subject: Fixed another unit test for Python 3. --- pystache/tests/test_specloader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py index 12ec57d..ac32dd6 100644 --- a/pystache/tests/test_specloader.py +++ b/pystache/tests/test_specloader.py @@ -185,10 +185,10 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): return _make_specloader() def test_init__defaults(self): - custom = self._make_specloader() + spec_loader = SpecLoader() # Check the loader attribute. - loader = custom.loader + loader = spec_loader.loader self.assertEqual(loader.extension, 'mustache') self.assertEqual(loader.file_encoding, sys.getdefaultencoding()) # TODO: finish testing the other Loader attributes. -- cgit v1.2.1 From 4af76cab4700897282f7a358697ae2395641f52f Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 16:41:43 -0700 Subject: Another Python 3 unit test change. --- pystache/renderer.py | 6 +++--- pystache/tests/test_renderer.py | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'pystache') diff --git a/pystache/renderer.py b/pystache/renderer.py index 3356ec1..3c82236 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -160,9 +160,9 @@ class Renderer(object): """ return unicode(self.escape(self._to_unicode_soft(s))) - def unicode(self, s, encoding=None): + def unicode(self, b, encoding=None): """ - Convert a string to unicode, using string_encoding and decode_errors. + Convert a byte string to unicode, using string_encoding and decode_errors. Raises: @@ -178,7 +178,7 @@ class Renderer(object): # TODO: Wrap UnicodeDecodeErrors with a message about setting # the string_encoding and decode_errors attributes. - return unicode(s, encoding, self.decode_errors) + return unicode(b, encoding, self.decode_errors) def _make_loader(self): """ diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index 3636bd5..3652aa7 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -393,6 +393,14 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): """ + def _make_renderer(self): + """ + Return a default Renderer instance for testing purposes. + + """ + renderer = Renderer(string_encoding='ascii', file_encoding='ascii') + return renderer + ## Test the engine's load_partial attribute. def test__load_partial__returns_unicode(self): @@ -442,13 +450,15 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): Test that literal uses the renderer's unicode function. """ - renderer = Renderer() - renderer.unicode = lambda s: s.upper() + renderer = self._make_renderer() + # This function + renderer.unicode = lambda b: unicode(b, encoding='ascii').upper() engine = renderer._make_render_engine() literal = engine.literal - self.assertEqual(literal("foo"), "FOO") + b = u"foo".encode("ascii") + self.assertEqual(literal(b), "FOO") def test__literal__handles_unicode(self): """ -- cgit v1.2.1 From 17919ab60d4ec41cfb9b2839653cc10e3517fa69 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 7 Apr 2012 19:26:33 -0700 Subject: More Python 3 unit test fixes. --- pystache/context.py | 9 +++++-- pystache/defaults.py | 5 ++-- pystache/parser.py | 2 +- pystache/renderengine.py | 2 +- pystache/renderer.py | 7 ++++++ pystache/tests/test_renderengine.py | 4 ++-- pystache/tests/test_renderer.py | 47 ++++++++++++++++++++++++++----------- 7 files changed, 54 insertions(+), 22 deletions(-) (limited to 'pystache') diff --git a/pystache/context.py b/pystache/context.py index 1621d61..d009fdf 100644 --- a/pystache/context.py +++ b/pystache/context.py @@ -5,11 +5,16 @@ Defines a Context class to represent mustache(5)'s notion of context. """ -class NotFound(object): pass +# This equals '__builtin__' in Python 2 and 'builtins' in Python 3. +_BUILTIN_MODULE = type(0).__module__ + + # We use this private global variable as a return value to represent a key # not being found on lookup. This lets us distinguish between the case # of a key's value being None with the case of a key not being found -- # without having to rely on exceptions (e.g. KeyError) for flow control. +class NotFound(object): + pass _NOT_FOUND = NotFound() @@ -34,7 +39,7 @@ def _get_value(item, key): # (e.g. catching KeyError). if key in item: return item[key] - elif type(item).__module__ != '__builtin__': + elif type(item).__module__ != _BUILTIN_MODULE: # Then we consider the argument an "object" for the purposes of # the spec. # diff --git a/pystache/defaults.py b/pystache/defaults.py index d0be493..bb989c5 100644 --- a/pystache/defaults.py +++ b/pystache/defaults.py @@ -44,8 +44,9 @@ SEARCH_DIRS = [os.curdir] # i.e. ['.'] # rendering templates (e.g. for tags enclosed in double braces). # Only unicode strings will be passed to this function. # -# The quote=True argument causes double quotes to be escaped, -# but not single quotes: +# The quote=True argument causes double quotes to be escaped in Python 2, +# but not single quotes, and both double quotes and single quotes to be +# escaped in Python 3: # # http://docs.python.org/dev/library/html.html#html.escape # http://docs.python.org/library/cgi.html#cgi.escape diff --git a/pystache/parser.py b/pystache/parser.py index d07ebf6..2b96249 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -131,7 +131,7 @@ class Parser(object): if tag_type == '/': if tag_key != section_key: - raise ParsingError("Section end tag mismatch: %s != %s" % (repr(tag_key), repr(section_key))) + raise ParsingError("Section end tag mismatch: %s != %s" % (tag_key, section_key)) return ParsedTemplate(parse_tree), template[start_index:match_index], end_index diff --git a/pystache/renderengine.py b/pystache/renderengine.py index 4361dca..9030de8 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -55,7 +55,7 @@ class RenderEngine(object): this class will not pass tag values to literal prior to passing them to this function. This allows for more flexibility, for example using a custom escape function that handles - incoming strings of type markupssafe.Markup differently + incoming strings of type markupsafe.Markup differently from plain unicode strings. """ diff --git a/pystache/renderer.py b/pystache/renderer.py index 3c82236..f16a95b 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -164,6 +164,13 @@ class Renderer(object): """ Convert a byte string to unicode, using string_encoding and decode_errors. + Arguments: + + b: a byte string. + + encoding: the name of an encoding. Defaults to the string_encoding + attribute for this instance. + Raises: TypeError: Because this method calls Python's built-in unicode() diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index f91d2a7..a5ac5dd 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -315,7 +315,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): try: self._assert_render(None, template) except ParsingError, err: - self.assertEqual(str(err), "Section end tag mismatch: u'section' != None") + self.assertEqual(str(err), "Section end tag mismatch: section != None") def test_section__end_tag_mismatch(self): """ @@ -326,7 +326,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): try: self._assert_render(None, template) except ParsingError, err: - self.assertEqual(str(err), "Section end tag mismatch: u'section_end' != u'section_start'") + self.assertEqual(str(err), "Section end tag mismatch: section_end != section_start") def test_section__context_values(self): """ diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index 3652aa7..2d9e67e 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -20,6 +20,22 @@ from pystache.tests.common import AssertStringMixin from pystache.tests.data.views import SayHello +def _make_renderer(): + """ + Return a default Renderer instance for testing purposes. + + """ + renderer = Renderer(string_encoding='ascii', file_encoding='ascii') + return renderer + + +def mock_unicode(b, encoding=None): + if encoding is None: + encoding = 'ascii' + u = unicode(b, encoding=encoding) + return u.upper() + + class RendererInitTestCase(unittest.TestCase): """ @@ -48,8 +64,12 @@ class RendererInitTestCase(unittest.TestCase): self.assertEqual(escape(">"), ">") self.assertEqual(escape('"'), """) - # Single quotes are not escaped. - self.assertEqual(escape("'"), "'") + # Single quotes are escaped in Python 3 but not Python 2. + if sys.version_info < (3, ): + expected = "'" + else: + expected = ''' + self.assertEqual(escape("'"), expected) def test_escape(self): escape = lambda s: "**" + s @@ -288,8 +308,8 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test passing a non-unicode template with non-ascii characters. """ - renderer = Renderer() - template = "déf" + renderer = _make_renderer() + template = u"déf".encode("utf-8") # Check that decode_errors and string_encoding are both respected. renderer.decode_errors = 'ignore' @@ -398,8 +418,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): Return a default Renderer instance for testing purposes. """ - renderer = Renderer(string_encoding='ascii', file_encoding='ascii') - return renderer + return _make_renderer() ## Test the engine's load_partial attribute. @@ -451,8 +470,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): """ renderer = self._make_renderer() - # This function - renderer.unicode = lambda b: unicode(b, encoding='ascii').upper() + renderer.unicode = mock_unicode engine = renderer._make_render_engine() literal = engine.literal @@ -516,12 +534,13 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): """ renderer = Renderer() - renderer.unicode = lambda s: s.upper() + renderer.unicode = mock_unicode engine = renderer._make_render_engine() escape = engine.escape - self.assertEqual(escape("foo"), "FOO") + b = u"foo".encode('ascii') + self.assertEqual(escape(b), "FOO") def test__escape__has_access_to_original_unicode_subclass(self): """ @@ -529,7 +548,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): """ renderer = Renderer() - renderer.escape = lambda s: type(s).__name__ + renderer.escape = lambda s: unicode(type(s).__name__) engine = renderer._make_render_engine() escape = engine.escape @@ -537,9 +556,9 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): class MyUnicode(unicode): pass - self.assertEqual(escape("foo"), "unicode") - self.assertEqual(escape(u"foo"), "unicode") - self.assertEqual(escape(MyUnicode("foo")), "MyUnicode") + self.assertEqual(escape(u"foo".encode('ascii')), unicode.__name__) + self.assertEqual(escape(u"foo"), unicode.__name__) + self.assertEqual(escape(MyUnicode("foo")), MyUnicode.__name__) def test__escape__returns_unicode(self): """ -- cgit v1.2.1 From 166f7c608c391ef7e91d1f4bf675e0b81b37ca05 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 14:57:15 -0700 Subject: Cleaned up test_mustachespec.py somewhat. --- pystache/tests/test_mustachespec.py | 48 ++++++++++--------------------------- 1 file changed, 12 insertions(+), 36 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py index 06e4dd8..ba87d2c 100644 --- a/pystache/tests/test_mustachespec.py +++ b/pystache/tests/test_mustachespec.py @@ -9,6 +9,7 @@ Creates a unittest.TestCase for the tests defined in the mustache spec. FILE_ENCODING = 'utf-8' # the encoding of the spec test files. +yaml = None try: # We try yaml first since it is more convenient when adding and modifying @@ -37,23 +38,22 @@ import os.path import unittest import pystache +from pystache import common from pystache.renderer import Renderer from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR -spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) - - -def parse(u, file_extension): +def parse(u): """ + Parse Arguments: u: a unicode string. """ # TODO: find a cleaner mechanism for choosing between the two. - if file_extension[0] == 'j': - # Then json. + if yaml is None: + # Then use json. # The only way to get the simplejson module to return unicode strings # is to pass it unicode. See, for example-- @@ -157,38 +157,14 @@ def buildTest(testData, spec_filename, parser): return test -for spec_path in spec_paths: +spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) +for path in spec_paths: - file_name = os.path.basename(spec_path) + file_name = os.path.basename(path) - # We use codecs.open() for pre Python 2.6 support and because it ports - # correctly to Python 3: - # - # "If pre-2.6 compatibility is needed, then you should use codecs.open() - # instead. This will make sure that you get back unicode strings in Python 2." - # - # (from http://docs.python.org/py3k/howto/pyporting.html#text-files ) - # - # TODO: share code here with pystache's open() code. - f = codecs.open(spec_path, 'r', encoding=FILE_ENCODING) - try: - u = f.read() - finally: - f.close() - - # The only way to get the simplejson module to return unicode strings - # is to pass it unicode. See, for example-- - # - # http://code.google.com/p/simplejson/issues/detail?id=40 - # - # and the documentation of simplejson.loads(): - # - # "If s is a str then decoded JSON strings that contain only ASCII - # characters may be parsed as str for performance and memory reasons. - # If your code expects only unicode the appropriate solution is - # decode s to unicode prior to calling loads." - # - spec_data = parse(u, file_extension) + b = common.read(path) + u = unicode(b, encoding=FILE_ENCODING) + spec_data = parse(u) tests = spec_data['tests'] -- cgit v1.2.1 From 3f893994a6233d996be6e037e59b1952a0fabed6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 15:50:01 -0700 Subject: Improved rendering logic around list-like section values for Python 3. --- pystache/renderengine.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/renderengine.py b/pystache/renderengine.py index 9030de8..e91d019 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -167,9 +167,28 @@ class RenderEngine(object): # TODO: should we check the arity? template = data(template) parsed_template = self._parse(template, delimiters=delims) - data = [ data ] - elif not hasattr(data, '__iter__') or isinstance(data, dict): - data = [ data ] + data = [data] + else: + # The cleanest, least brittle way of determining whether + # something supports iteration is by trying to call iter() on it: + # + # http://docs.python.org/library/functions.html#iter + # + # It is not sufficient, for example, to check whether the item + # implements __iter__ () (the iteration protocol). There is + # also __getitem__() (the sequence protocol). In Python 2, + # strings do not implement __iter__(), but in Python 3 they do. + try: + iter(data) + except TypeError: + # Then the value does not support iteration. + data = [data] + else: + # We treat the value as a list (but do not treat strings + # and dicts as lists). + if isinstance(data, (basestring, dict)): + data = [data] + # Otherwise, leave it alone. parts = [] for element in data: -- cgit v1.2.1 From 642cade95bdbdcdec04aba73d3b49bbf37431d7e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 16:13:29 -0700 Subject: Fixed the built-in type integer unit test for Python 3. --- pystache/tests/test_renderengine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index a5ac5dd..52f4bfe 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -230,7 +230,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): # # we need to resort to built-in attributes (double-underscored) on # the integer type. - self._assert_builtin_type(15, '__hex__', '0xf', u'999') + self._assert_builtin_type(15, '__neg__', -15, u'999') def test_interpolation__built_in_type__list(self): """ -- cgit v1.2.1 From 50d6a535687ae29e9dcf4707bd03dad4f885f933 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 18:10:55 -0700 Subject: Exposed an html_escape function in tests.common for consistent Python 2/3 testing. --- pystache/tests/common.py | 14 ++++++++++++++ pystache/tests/test_pystache.py | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 538784b..3bbe1e4 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -9,8 +9,10 @@ import os import examples import pystache +from pystache import defaults +_DEFAULT_TAG_ESCAPE = defaults.TAG_ESCAPE _TESTS_DIR = os.path.dirname(pystache.tests.__file__) DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. @@ -20,6 +22,18 @@ PROJECT_DIR = os.path.join(SOURCE_DIR, '..') SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') +def html_escape(u): + """ + An html escape function that behaves the same in both Python 2 and 3. + + This function is needed because single quotes are escaped in Python 3 + (to '''), but not in Python 2. + + """ + u = _DEFAULT_TAG_ESCAPE(u) + return u.replace("'", ''') + + def get_data_path(file_name): return os.path.join(DATA_DIR, file_name) diff --git a/pystache/tests/test_pystache.py b/pystache/tests/test_pystache.py index e241842..5447f8d 100644 --- a/pystache/tests/test_pystache.py +++ b/pystache/tests/test_pystache.py @@ -1,12 +1,23 @@ # encoding: utf-8 import unittest + import pystache +from pystache import defaults from pystache import renderer +from pystache.tests.common import html_escape class PystacheTests(unittest.TestCase): + + def setUp(self): + self.original_escape = defaults.TAG_ESCAPE + defaults.TAG_ESCAPE = html_escape + + def tearDown(self): + defaults.TAG_ESCAPE = self.original_escape + def _assert_rendered(self, expected, template, context): actual = pystache.render(template, context) self.assertEqual(actual, expected) @@ -54,7 +65,7 @@ class PystacheTests(unittest.TestCase): context = { 'set': True } self._assert_rendered("Ready set go!", template, context) - non_strings_expected = """(123 & ['something'])(chris & 0.9)""" + non_strings_expected = """(123 & ['something'])(chris & 0.9)""" def test_non_strings(self): template = "{{#stats}}({{key}} & {{value}}){{/stats}}" -- cgit v1.2.1 From c414a5ca3f974ef5d7063ec024f22cb7f7e9b8ac Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 18:22:02 -0700 Subject: Made 6 more tests pass in Python 3 (and simultaneously, Python 2).. --- pystache/tests/common.py | 5 +++++ pystache/tests/test_loader.py | 13 +++++++++++++ 2 files changed, 18 insertions(+) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 3bbe1e4..446c0e5 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -12,6 +12,7 @@ import pystache from pystache import defaults +# Save a reference to the original function to avoid recursion. _DEFAULT_TAG_ESCAPE = defaults.TAG_ESCAPE _TESTS_DIR = os.path.dirname(pystache.tests.__file__) @@ -29,6 +30,10 @@ def html_escape(u): This function is needed because single quotes are escaped in Python 3 (to '''), but not in Python 2. + The global defaults.TAG_ESCAPE can be set to this function in the + setUp() and tearDown() of unittest test cases, for example, for + consistent test results. + """ u = _DEFAULT_TAG_ESCAPE(u) return u.replace("'", ''') diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index efd09c5..1fc8dbe 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -16,6 +16,19 @@ from pystache.loader import Loader class LoaderTests(unittest.TestCase, AssertStringMixin): + # Switching to standard encodings allows for consistent test + # results across Python 2/3. + def setUp(self): + self.original_string_encoding = defaults.STRING_ENCODING + self.original_file_encoding = defaults.FILE_ENCODING + + defaults.STRING_ENCODING = 'ascii' + defaults.FILE_ENCODING = 'ascii' + + def tearDown(self): + defaults.STRING_ENCODING = self.original_string_encoding + defaults.FILE_ENCODING = self.original_file_encoding + def test_init__extension(self): loader = Loader(extension='foo') self.assertEqual(loader.extension, 'foo') -- cgit v1.2.1 From b06ea722e284e867dbc31e299bb1a1e9c5ba406c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 18:32:07 -0700 Subject: Made a test_context pass in Python 3. --- pystache/tests/test_context.py | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py index 2b6846f..bcf72de 100644 --- a/pystache/tests/test_context.py +++ b/pystache/tests/test_context.py @@ -85,9 +85,11 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): Test that dictionary attributes are not checked. """ - item = {} - attr_name = "keys" - self.assertEqual(getattr(item, attr_name)(), []) + item = {1: 2, 3: 4} + # I was not able to find a "public" attribute of dict that is + # the same across Python 2/3. + attr_name = "__len__" + self.assertEqual(getattr(item, attr_name)(), 2) self.assertNotFound(item, attr_name) def test_dictionary__dict_subclass(self): @@ -154,25 +156,20 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): """ class MyInt(int): pass - item1 = MyInt(10) - item2 = 10 - - try: - item2.real - except AttributeError: - # Then skip this unit test. The numeric type hierarchy was - # added only in Python 2.6, in which case integers inherit - # from complex numbers the "real" attribute, etc: - # - # http://docs.python.org/library/numbers.html - # - return - - self.assertEqual(item1.real, 10) - self.assertEqual(item2.real, 10) - - self.assertEqual(_get_value(item1, 'real'), 10) - self.assertNotFound(item2, 'real') + cust_int = MyInt(10) + pure_int = 10 + + # We have to use a built-in method like __neg__ because "public" + # attributes like "real" were not added to Python until Python 2.6, + # when the numeric type hierarchy was added: + # + # http://docs.python.org/library/numbers.html + # + self.assertEqual(cust_int.__neg__(), -10) + self.assertEqual(pure_int.__neg__(), -10) + + self.assertEqual(_get_value(cust_int, '__neg__'), -10) + self.assertNotFound(pure_int, '__neg__') def test_built_in_type__string(self): """ -- cgit v1.2.1 From b042d08429b95d76589ea69d14d7a44bcb32c3a1 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 18:49:38 -0700 Subject: More unit tests for Python 3: type-checking in Renderer.render() for byte strings. --- pystache/renderer.py | 15 ++++++++++++++- pystache/tests/test_renderer.py | 12 ++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) (limited to 'pystache') diff --git a/pystache/renderer.py b/pystache/renderer.py index f16a95b..93be33b 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -5,6 +5,8 @@ This module provides a Renderer class to render templates. """ +import sys + from pystache import defaults from pystache.context import Context from pystache.loader import Loader @@ -13,6 +15,17 @@ from pystache.specloader import SpecLoader from pystache.template_spec import TemplateSpec +# TODO: come up with a better solution for this. One of the issues here +# is that in Python 3 there is no common base class for unicode strings +# and byte strings, and 2to3 seems to convert all of "str", "unicode", +# and "basestring" to Python 3's "str". +if sys.version_info < (3, ): + _STRING_TYPES = basestring +else: + # The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3. + _STRING_TYPES = (unicode, type(u"a".encode('utf-8'))) + + class Renderer(object): """ @@ -336,7 +349,7 @@ class Renderer(object): all items in the *context list. """ - if isinstance(template, basestring): + if isinstance(template, _STRING_TYPES): return self._render_string(template, *context, **kwargs) # Otherwise, we assume the template is an object. diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index 2d9e67e..31ac1d4 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -180,13 +180,13 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() - s = "é" + b = u"é".encode('utf-8') renderer.string_encoding = "ascii" - self.assertRaises(UnicodeDecodeError, renderer.unicode, s) + self.assertRaises(UnicodeDecodeError, renderer.unicode, b) renderer.string_encoding = "utf-8" - self.assertEqual(renderer.unicode(s), u"é") + self.assertEqual(renderer.unicode(b), u"é") def test_unicode__decode_errors(self): """ @@ -195,14 +195,14 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ renderer = Renderer() renderer.string_encoding = "ascii" - s = "déf" + b = u"déf".encode('utf-8') renderer.decode_errors = "ignore" - self.assertEqual(renderer.unicode(s), "df") + self.assertEqual(renderer.unicode(b), "df") renderer.decode_errors = "replace" # U+FFFD is the official Unicode replacement character. - self.assertEqual(renderer.unicode(s), u'd\ufffd\ufffdf') + self.assertEqual(renderer.unicode(b), u'd\ufffd\ufffdf') ## Test the _make_loader() method. -- cgit v1.2.1 From f9bda3a5ce2725ca2e2b5a89fbd8a1b1e7642a90 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 19:16:21 -0700 Subject: More Python 3 unit test fixes; README doctests also now working. --- pystache/__init__.py | 2 +- pystache/parser.py | 2 +- pystache/renderengine.py | 2 +- pystache/tests/test_locator.py | 2 +- pystache/tests/test_renderengine.py | 28 +++++++++++++++++++++++----- 5 files changed, 27 insertions(+), 9 deletions(-) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index daf7f52..c1070e3 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -1,2 +1,2 @@ # We keep all initialization code in a separate module. -from init import * +from pystache.init import * diff --git a/pystache/parser.py b/pystache/parser.py index 2b96249..ccb6827 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -9,7 +9,7 @@ This module is only meant for internal use by the renderengine module. import re -from parsed import ParsedTemplate +from pystache.parsed import ParsedTemplate DEFAULT_DELIMITERS = ('{{', '}}') diff --git a/pystache/renderengine.py b/pystache/renderengine.py index e91d019..d9c822c 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -7,7 +7,7 @@ Defines a class responsible for rendering logic. import re -from parser import Parser +from pystache.parser import Parser class RenderEngine(object): diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py index 2ae07ca..ddebef8 100644 --- a/pystache/tests/test_locator.py +++ b/pystache/tests/test_locator.py @@ -15,7 +15,7 @@ from pystache.loader import Loader as Reader from pystache.locator import Locator from pystache.tests.common import DATA_DIR -from data.views import SayHello +from pystache.tests.data.views import SayHello class LocatorTests(unittest.TestCase): diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index 52f4bfe..3c63cfb 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -14,6 +14,27 @@ from pystache.renderengine import RenderEngine from pystache.tests.common import AssertStringMixin +def mock_literal(s): + """ + For use as the literal keyword argument to the RenderEngine constructor. + + Arguments: + + s: a byte string or unicode string. + + """ + if isinstance(s, unicode): + # Strip off unicode super classes, if present. + u = unicode(s) + else: + u = unicode(s, encoding='ascii') + + # We apply upper() to make sure we are actually using our custom + # function in the tests + return u.upper() + + + class RenderEngineTestCase(unittest.TestCase): """Test the RenderEngine class.""" @@ -154,12 +175,9 @@ class RenderTests(unittest.TestCase, AssertStringMixin): Test a context value that is not a basestring instance. """ - # We use include upper() to make sure we are actually using - # our custom function in the tests - to_unicode = lambda s: unicode(s, encoding='ascii').upper() engine = self._engine() - engine.escape = to_unicode - engine.literal = to_unicode + engine.escape = mock_literal + engine.literal = mock_literal self.assertRaises(TypeError, engine.literal, 100) -- cgit v1.2.1 From a8e390cb48a2f4757774c570c81e94fd0d2b676f Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 20:52:38 -0700 Subject: All unit tests except for spec tests now work in both Python 2/3. --- pystache/loader.py | 22 ++++++++++++++-------- pystache/renderer.py | 5 +++-- pystache/tests/common.py | 25 +++++++++++++++++++++++++ pystache/tests/test_loader.py | 41 ++++++++++++++--------------------------- pystache/tests/test_renderer.py | 29 ++++++++++++++--------------- 5 files changed, 70 insertions(+), 52 deletions(-) (limited to 'pystache') diff --git a/pystache/loader.py b/pystache/loader.py index 3a7d02c..d17bc2e 100644 --- a/pystache/loader.py +++ b/pystache/loader.py @@ -13,14 +13,20 @@ from pystache import defaults from pystache.locator import Locator -def _default_to_unicode(s, encoding=None): - """ - Raises a TypeError exception if the given string is already unicode. +# We make a function so that the current defaults take effect. +# TODO: revisit whether this is necessary. +# TODO: change assertNotEquals to assertNotEqual everywhere. - """ - if encoding is None: - encoding = defaults.STRING_ENCODING - return unicode(s, encoding, defaults.DECODE_ERRORS) +def _make_to_unicode(): + def to_unicode(s, encoding=None): + """ + Raises a TypeError exception if the given string is already unicode. + + """ + if encoding is None: + encoding = defaults.STRING_ENCODING + return unicode(s, encoding, defaults.DECODE_ERRORS) + return to_unicode class Loader(object): @@ -68,7 +74,7 @@ class Loader(object): search_dirs = defaults.SEARCH_DIRS if to_unicode is None: - to_unicode = _default_to_unicode + to_unicode = _make_to_unicode() self.extension = extension self.file_encoding = file_encoding diff --git a/pystache/renderer.py b/pystache/renderer.py index 93be33b..d55c72f 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -40,8 +40,9 @@ class Renderer(object): >>> partials = {'partial': 'Hello, {{thing}}!'} >>> renderer = Renderer(partials=partials) - >>> renderer.render('{{>partial}}', {'thing': 'world'}) - u'Hello, world!' + >>> # We apply print to make the test work in Python 3 after 2to3. + >>> print renderer.render('{{>partial}}', {'thing': 'world'}) + Hello, world! """ diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 446c0e5..8df6379 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -88,3 +88,28 @@ class AssertIsMixin: # http://docs.python.org/library/unittest.html#unittest.TestCase.assertIsNone def assertIs(self, first, second): self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second))) + + +class SetupDefaults(object): + + """ + Mix this class in to a unittest.TestCase for standard defaults. + + This class allows for consistent test results across Python 2/3. + + """ + + def setup_defaults(self): + self.original_decode_errors = defaults.DECODE_ERRORS + self.original_file_encoding = defaults.FILE_ENCODING + self.original_string_encoding = defaults.STRING_ENCODING + + defaults.DECODE_ERRORS = 'strict' + defaults.FILE_ENCODING = 'ascii' + defaults.STRING_ENCODING = 'ascii' + + def teardown_defaults(self): + defaults.DECODE_ERRORS = self.original_decode_errors + defaults.FILE_ENCODING = self.original_file_encoding + defaults.STRING_ENCODING = self.original_string_encoding + diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index 1fc8dbe..2377025 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -9,25 +9,18 @@ import os import sys import unittest -from pystache.tests.common import AssertStringMixin, DATA_DIR +from pystache.tests.common import AssertStringMixin, DATA_DIR, SetupDefaults from pystache import defaults from pystache.loader import Loader -class LoaderTests(unittest.TestCase, AssertStringMixin): +class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): - # Switching to standard encodings allows for consistent test - # results across Python 2/3. def setUp(self): - self.original_string_encoding = defaults.STRING_ENCODING - self.original_file_encoding = defaults.FILE_ENCODING - - defaults.STRING_ENCODING = 'ascii' - defaults.FILE_ENCODING = 'ascii' + self.setup_defaults() def tearDown(self): - defaults.STRING_ENCODING = self.original_string_encoding - defaults.FILE_ENCODING = self.original_file_encoding + self.teardown_defaults() def test_init__extension(self): loader = Loader(extension='foo') @@ -63,25 +56,19 @@ class LoaderTests(unittest.TestCase, AssertStringMixin): decode_errors = defaults.DECODE_ERRORS string_encoding = defaults.STRING_ENCODING - nonascii = 'abcdé' + nonascii = u'abcdé'.encode('utf-8') - try: - defaults.DECODE_ERRORS = 'strict' - defaults.STRING_ENCODING = 'ascii' - loader = Loader() - self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii) + loader = Loader() + self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii) - defaults.DECODE_ERRORS = 'ignore' - loader = Loader() - self.assertString(loader.to_unicode(nonascii), u'abcd') + defaults.DECODE_ERRORS = 'ignore' + loader = Loader() + self.assertString(loader.to_unicode(nonascii), u'abcd') - defaults.STRING_ENCODING = 'utf-8' - loader = Loader() - self.assertString(loader.to_unicode(nonascii), u'abcdé') + defaults.STRING_ENCODING = 'utf-8' + loader = Loader() + self.assertString(loader.to_unicode(nonascii), u'abcdé') - finally: - defaults.DECODE_ERRORS = decode_errors - defaults.STRING_ENCODING = string_encoding def _get_path(self, filename): return os.path.join(DATA_DIR, filename) @@ -126,10 +113,10 @@ class LoaderTests(unittest.TestCase, AssertStringMixin): Test unicode(): encoding attribute. """ + # TODO: rename reader to loader everywhere in this module. reader = Loader() non_ascii = u'abcdé'.encode('utf-8') - self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii) def to_unicode(s, encoding=None): diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index 31ac1d4..c022079 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -15,8 +15,7 @@ from pystache import Renderer from pystache import TemplateSpec from pystache.loader import Loader -from pystache.tests.common import get_data_path -from pystache.tests.common import AssertStringMixin +from pystache.tests.common import get_data_path, AssertStringMixin from pystache.tests.data.views import SayHello @@ -179,7 +178,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test that the string_encoding attribute is respected. """ - renderer = Renderer() + renderer = self._renderer() b = u"é".encode('utf-8') renderer.string_encoding = "ascii" @@ -193,7 +192,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test that the decode_errors attribute is respected. """ - renderer = Renderer() + renderer = self._renderer() renderer.string_encoding = "ascii" b = u"déf".encode('utf-8') @@ -211,7 +210,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test that _make_loader() returns a Loader. """ - renderer = Renderer() + renderer = self._renderer() loader = renderer._make_loader() self.assertEqual(type(loader), Loader) @@ -223,7 +222,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ unicode_ = lambda x: x - renderer = Renderer() + renderer = self._renderer() renderer.file_encoding = 'enc' renderer.file_extension = 'ext' renderer.unicode = unicode_ @@ -241,22 +240,22 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Check that render() returns a string of type unicode. """ - renderer = Renderer() + renderer = self._renderer() rendered = renderer.render('foo') self.assertEqual(type(rendered), unicode) def test_render__unicode(self): - renderer = Renderer() + renderer = self._renderer() actual = renderer.render(u'foo') self.assertEqual(actual, u'foo') def test_render__str(self): - renderer = Renderer() + renderer = self._renderer() actual = renderer.render('foo') self.assertEqual(actual, 'foo') def test_render__non_ascii_character(self): - renderer = Renderer() + renderer = self._renderer() actual = renderer.render(u'Poincaré') self.assertEqual(actual, u'Poincaré') @@ -265,7 +264,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test render(): passing a context. """ - renderer = Renderer() + renderer = self._renderer() self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}), 'Hi Mom') def test_render__context_and_kwargs(self): @@ -273,7 +272,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test render(): passing a context and **kwargs. """ - renderer = Renderer() + renderer = self._renderer() template = 'Hi {{person1}} and {{person2}}' self.assertEqual(renderer.render(template, {'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad') @@ -282,7 +281,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test render(): passing **kwargs and no context. """ - renderer = Renderer() + renderer = self._renderer() self.assertEqual(renderer.render('Hi {{person}}', person='Mom'), 'Hi Mom') def test_render__context_and_kwargs__precedence(self): @@ -290,7 +289,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): Test render(): **kwargs takes precedence over context. """ - renderer = Renderer() + renderer = self._renderer() self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}, person='Dad'), 'Hi Dad') def test_render__kwargs_does_not_modify_context(self): @@ -299,7 +298,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): """ context = {} - renderer = Renderer() + renderer = self._renderer() renderer.render('Hi {{person}}', context=context, foo="bar") self.assertEqual(context, {}) -- cgit v1.2.1 From 1fd7f3f0e59ef53172c8a0d6ca611b7206f3da1c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 20:54:09 -0700 Subject: Changed the deprecated (in Python 3) assertNotEquals to assertNotEqual everywhere. --- pystache/loader.py | 1 - pystache/tests/test_context.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/loader.py b/pystache/loader.py index d17bc2e..0fdadc5 100644 --- a/pystache/loader.py +++ b/pystache/loader.py @@ -15,7 +15,6 @@ from pystache.locator import Locator # We make a function so that the current defaults take effect. # TODO: revisit whether this is necessary. -# TODO: change assertNotEquals to assertNotEqual everywhere. def _make_to_unicode(): def to_unicode(s, encoding=None): diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py index bcf72de..9856fee 100644 --- a/pystache/tests/test_context.py +++ b/pystache/tests/test_context.py @@ -69,7 +69,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): return "bar" item = {"foo": foo_callable} - self.assertNotEquals(_get_value(item, "foo"), "bar") + self.assertNotEqual(_get_value(item, "foo"), "bar") self.assertTrue(_get_value(item, "foo") is foo_callable) def test_dictionary__key_missing(self): -- cgit v1.2.1 From 4be82fd12cacfb76f5742b618d154e500ab392b0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 8 Apr 2012 20:57:34 -0700 Subject: Addressed TODO to rename reader to loader in test_loader.py. --- pystache/tests/test_loader.py | 49 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 25 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index 2377025..c47239c 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -1,7 +1,7 @@ # encoding: utf-8 """ -Unit tests of reader.py. +Unit tests of loader.py. """ @@ -78,8 +78,8 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): Test unicode(): default arguments with str input. """ - reader = Loader() - actual = reader.unicode("foo") + loader = Loader() + actual = loader.unicode("foo") self.assertString(actual, u"foo") @@ -88,8 +88,8 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): Test unicode(): default arguments with unicode input. """ - reader = Loader() - actual = reader.unicode(u"foo") + loader = Loader() + actual = loader.unicode(u"foo") self.assertString(actual, u"foo") @@ -103,8 +103,8 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): s = UnicodeSubclass(u"foo") - reader = Loader() - actual = reader.unicode(s) + loader = Loader() + actual = loader.unicode(s) self.assertString(actual, u"foo") @@ -113,32 +113,31 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): Test unicode(): encoding attribute. """ - # TODO: rename reader to loader everywhere in this module. - reader = Loader() + loader = Loader() non_ascii = u'abcdé'.encode('utf-8') - self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii) + self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii) def to_unicode(s, encoding=None): if encoding is None: encoding = 'utf-8' return unicode(s, encoding) - reader.to_unicode = to_unicode - self.assertString(reader.unicode(non_ascii), u"abcdé") + loader.to_unicode = to_unicode + self.assertString(loader.unicode(non_ascii), u"abcdé") def test_unicode__encoding_argument(self): """ Test unicode(): encoding argument. """ - reader = Loader() + loader = Loader() non_ascii = u'abcdé'.encode('utf-8') - self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii) + self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii) - actual = reader.unicode(non_ascii, encoding='utf-8') + actual = loader.unicode(non_ascii, encoding='utf-8') self.assertString(actual, u'abcdé') # TODO: check the read() unit tests. @@ -147,9 +146,9 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): Test read(). """ - reader = Loader() + loader = Loader() path = self._get_path('ascii.mustache') - actual = reader.read(path) + actual = loader.read(path) self.assertString(actual, u'ascii: abc') def test_read__file_encoding__attribute(self): @@ -171,25 +170,25 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults): Test read(): encoding argument respected. """ - reader = Loader() + loader = Loader() path = self._get_path('non_ascii.mustache') - self.assertRaises(UnicodeDecodeError, reader.read, path) + self.assertRaises(UnicodeDecodeError, loader.read, path) - actual = reader.read(path, encoding='utf-8') + actual = loader.read(path, encoding='utf-8') self.assertString(actual, u'non-ascii: é') - def test_reader__to_unicode__attribute(self): + def test_loader__to_unicode__attribute(self): """ Test read(): to_unicode attribute respected. """ - reader = Loader() + loader = Loader() path = self._get_path('non_ascii.mustache') - self.assertRaises(UnicodeDecodeError, reader.read, path) + self.assertRaises(UnicodeDecodeError, loader.read, path) - #reader.decode_errors = 'ignore' - #actual = reader.read(path) + #loader.decode_errors = 'ignore' + #actual = loader.read(path) #self.assertString(actual, u'non-ascii: ') -- cgit v1.2.1 From f124d46ebdf8a9b93b93070d85cae57ef6c971a7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 9 Apr 2012 03:18:30 -0700 Subject: Eliminated the nosetests hack around the load_tests protocol function. --- pystache/tests/test_doctests.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index b9d9895..6bcfcf2 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -34,23 +34,17 @@ text_file_paths = ['README.rst'] # # python setup.py test # -# TODO: find a substitute for the load_tests protocol for Python versions -# before version 2.7. -# -# TODO: prevent the load_tests() function below from being interpreted as a -# unittest when running nosetests. This is causing the test count to be -# different between `python setup.py test` and `python setup.py nosetests`. +# Normally, nosetests would interpret this function as a test case (because +# its name matches the test regular expression) and call it with zero arguments +# as opposed to the required three. However, we are able to exclude it with +# an entry like the following in setup.cfg: # -# HACK: Allowing load_tests() to be called without arguments is a hack -# to allow unit tests to be run with nose's nosetests without error. -# Otherwise, nose interprets the following function as a test case, -# raising the following error: +# exclude=load_tests # -# TypeError: load_tests() takes exactly 3 arguments (0 given) +# TODO: find a substitute for the load_tests protocol for Python versions +# before version 2.7. # -def load_tests(loader=None, tests=None, ignore=None): - if loader is None: - return +def load_tests(loader, tests, ignore): # Since module_relative is False in our calls to DocFileSuite below, # paths should be OS-specific. Moreover, we choose absolute paths # so that the current working directory does not come into play. -- cgit v1.2.1 From 6d2f1b4871e56013d6bdd1672445d731e4009475 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 10 Apr 2012 18:49:48 -0700 Subject: Got tox working with Python 2.4 through 3.2 (2.4, 2.5, 2.6, 2.7, 3.2). --- pystache/init.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/init.py b/pystache/init.py index b285a5c..a25ca4b 100644 --- a/pystache/init.py +++ b/pystache/init.py @@ -9,8 +9,9 @@ from pystache.renderer import Renderer from pystache.template_spec import TemplateSpec -__all__ = ['render', 'Renderer', 'TemplateSpec'] +__all__ = ['__version__', 'render', 'Renderer', 'TemplateSpec'] +__version__ = '0.5.0-rc' # Also change in setup.py. def render(template, context=None, **kwargs): """ -- cgit v1.2.1 From 84e24e5e8451d1845129c4e9d7c3fcf10995ca08 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 10 Apr 2012 22:31:28 -0700 Subject: Tests now pass with Python 3.1. --- pystache/defaults.py | 7 +++++++ pystache/tests/test_renderer.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/defaults.py b/pystache/defaults.py index bb989c5..e12e635 100644 --- a/pystache/defaults.py +++ b/pystache/defaults.py @@ -11,6 +11,13 @@ does not otherwise specify a value. try: # Python 3.2 deprecates cgi.escape() and adds the html module as a replacement. import html + try: + # We also need to verify the existence of the escape() method + # due to the following issue: + # http://bugs.python.org/issue14545 + html.escape + except AttributeError: + raise ImportError("html.escape does not exist") except ImportError: import cgi as html diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index c022079..64a4325 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -63,8 +63,8 @@ class RendererInitTestCase(unittest.TestCase): self.assertEqual(escape(">"), ">") self.assertEqual(escape('"'), """) - # Single quotes are escaped in Python 3 but not Python 2. - if sys.version_info < (3, ): + # Single quotes are escaped only in Python 3.2 and later. + if sys.version_info < (3, 2): expected = "'" else: expected = ''' -- cgit v1.2.1 From 0bf6ca1f6ce57d1ecfbfffd6c5e5ce9478ba7387 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 10 Apr 2012 23:54:28 -0700 Subject: Bump version from 0.5.0-rc to 0.5.0 prior to PyPI release: issue #101 --- pystache/init.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/init.py b/pystache/init.py index b285a5c..0e9cc87 100644 --- a/pystache/init.py +++ b/pystache/init.py @@ -9,7 +9,9 @@ from pystache.renderer import Renderer from pystache.template_spec import TemplateSpec -__all__ = ['render', 'Renderer', 'TemplateSpec'] +__all__ = ['__version__', 'render', 'Renderer', 'TemplateSpec'] + +__version__ = '0.5.0' def render(template, context=None, **kwargs): -- cgit v1.2.1 From 4f2ab143248d40bede4004c6f8d7d983771607cf Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 11 Apr 2012 02:38:52 -0700 Subject: Cleaned up code around html.escape() in pystache.defaults. --- pystache/defaults.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) (limited to 'pystache') diff --git a/pystache/defaults.py b/pystache/defaults.py index e12e635..fcd04c3 100644 --- a/pystache/defaults.py +++ b/pystache/defaults.py @@ -9,17 +9,10 @@ does not otherwise specify a value. """ try: - # Python 3.2 deprecates cgi.escape() and adds the html module as a replacement. - import html - try: - # We also need to verify the existence of the escape() method - # due to the following issue: - # http://bugs.python.org/issue14545 - html.escape - except AttributeError: - raise ImportError("html.escape does not exist") + # Python 3.2 adds html.escape() and deprecates cgi.escape(). + from html import escape except ImportError: - import cgi as html + from cgi import escape import os import sys @@ -51,14 +44,14 @@ SEARCH_DIRS = [os.curdir] # i.e. ['.'] # rendering templates (e.g. for tags enclosed in double braces). # Only unicode strings will be passed to this function. # -# The quote=True argument causes double quotes to be escaped in Python 2, -# but not single quotes, and both double quotes and single quotes to be -# escaped in Python 3: +# The quote=True argument causes double but not single quotes to be escaped +# in Python 3.1 and earlier, and both double and single quotes to be +# escaped in Python 3.2 and later: # -# http://docs.python.org/dev/library/html.html#html.escape # http://docs.python.org/library/cgi.html#cgi.escape +# http://docs.python.org/dev/library/html.html#html.escape # -TAG_ESCAPE = lambda u: html.escape(u, quote=True) +TAG_ESCAPE = lambda u: escape(u, quote=True) # The default template extension. TEMPLATE_EXTENSION = 'mustache' -- cgit v1.2.1 From 28a74f3c246adeb0951a0bc494349c033245d1c5 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 11 Apr 2012 03:48:06 -0700 Subject: Spacing. --- pystache/commands.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pystache') diff --git a/pystache/commands.py b/pystache/commands.py index 1801d40..e9fe490 100644 --- a/pystache/commands.py +++ b/pystache/commands.py @@ -78,4 +78,3 @@ def main(sys_argv): if __name__=='__main__': main(sys.argv) - -- cgit v1.2.1 From b56f03c4d286ce4386a9c6c3109082d304f7c947 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 13 Apr 2012 04:37:55 -0700 Subject: Make ImportErrors that occur while loading doctests easier to diagnose. --- pystache/tests/test_doctests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 6bcfcf2..bb7def8 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -13,6 +13,7 @@ Creates unittest.TestSuite instances for the doctests in the project. import os import doctest import pkgutil +import traceback import unittest import pystache @@ -82,7 +83,15 @@ def _get_module_doctests(): continue # The loader is a pkgutil.ImpLoader instance. loader = importer.find_module(module_name) - module = loader.load_module(module_name) + try: + module = loader.load_module(module_name) + except ImportError, e: + # In some situations, the test harness was swallowing and/or + # suppressing the display of the stack trace when errors + # occurred here. The following code makes errors occurring here + # easier to troubleshoot. + details = "".join(traceback.format_exception(*sys.exc_info())) + raise ImportError(details) modules.append(module) return modules -- cgit v1.2.1 From eacbdb3b6dacfbd86be9c07f18cedb2e3611a071 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 13 Apr 2012 04:50:05 -0700 Subject: Moved commands.py into a commands sub-package. --- pystache/commands.py | 80 ----------------------------------------- pystache/commands/__init__.py | 0 pystache/commands/render.py | 80 +++++++++++++++++++++++++++++++++++++++++ pystache/tests/test_commands.py | 2 +- 4 files changed, 81 insertions(+), 81 deletions(-) delete mode 100644 pystache/commands.py create mode 100644 pystache/commands/__init__.py create mode 100644 pystache/commands/render.py (limited to 'pystache') diff --git a/pystache/commands.py b/pystache/commands.py deleted file mode 100644 index e9fe490..0000000 --- a/pystache/commands.py +++ /dev/null @@ -1,80 +0,0 @@ -# coding: utf-8 - -""" -This module provides command-line access to pystache. - -Run this script using the -h option for command-line help. - -""" - - -try: - import json -except: - # The json module is new in Python 2.6, whereas simplejson is - # compatible with earlier versions. - import simplejson as json - -# The optparse module is deprecated in Python 2.7 in favor of argparse. -# However, argparse is not available in Python 2.6 and earlier. -from optparse import OptionParser -import sys - -# We use absolute imports here to allow use of this script from its -# location in source control (e.g. for development purposes). -# Otherwise, the following error occurs: -# -# ValueError: Attempted relative import in non-package -# -from pystache.renderer import Renderer - - -USAGE = """\ -%prog [-h] template context - -Render a mustache template with the given context. - -positional arguments: - template A filename or template string. - context A filename or JSON string.""" - - -def parse_args(sys_argv, usage): - """ - Return an OptionParser for the script. - - """ - args = sys_argv[1:] - - parser = OptionParser(usage=usage) - options, args = parser.parse_args(args) - - template, context = args - - return template, context - - -def main(sys_argv): - template, context = parse_args(sys_argv, USAGE) - - if template.endswith('.mustache'): - template = template[:-9] - - renderer = Renderer() - - try: - template = renderer.load_template(template) - except IOError: - pass - - try: - context = json.load(open(context)) - except IOError: - context = json.loads(context) - - rendered = renderer.render(template, context) - print rendered - - -if __name__=='__main__': - main(sys.argv) diff --git a/pystache/commands/__init__.py b/pystache/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pystache/commands/render.py b/pystache/commands/render.py new file mode 100644 index 0000000..e9fe490 --- /dev/null +++ b/pystache/commands/render.py @@ -0,0 +1,80 @@ +# coding: utf-8 + +""" +This module provides command-line access to pystache. + +Run this script using the -h option for command-line help. + +""" + + +try: + import json +except: + # The json module is new in Python 2.6, whereas simplejson is + # compatible with earlier versions. + import simplejson as json + +# The optparse module is deprecated in Python 2.7 in favor of argparse. +# However, argparse is not available in Python 2.6 and earlier. +from optparse import OptionParser +import sys + +# We use absolute imports here to allow use of this script from its +# location in source control (e.g. for development purposes). +# Otherwise, the following error occurs: +# +# ValueError: Attempted relative import in non-package +# +from pystache.renderer import Renderer + + +USAGE = """\ +%prog [-h] template context + +Render a mustache template with the given context. + +positional arguments: + template A filename or template string. + context A filename or JSON string.""" + + +def parse_args(sys_argv, usage): + """ + Return an OptionParser for the script. + + """ + args = sys_argv[1:] + + parser = OptionParser(usage=usage) + options, args = parser.parse_args(args) + + template, context = args + + return template, context + + +def main(sys_argv): + template, context = parse_args(sys_argv, USAGE) + + if template.endswith('.mustache'): + template = template[:-9] + + renderer = Renderer() + + try: + template = renderer.load_template(template) + except IOError: + pass + + try: + context = json.load(open(context)) + except IOError: + context = json.loads(context) + + rendered = renderer.render(template, context) + print rendered + + +if __name__=='__main__': + main(sys.argv) diff --git a/pystache/tests/test_commands.py b/pystache/tests/test_commands.py index fc11f30..2529d25 100644 --- a/pystache/tests/test_commands.py +++ b/pystache/tests/test_commands.py @@ -8,7 +8,7 @@ Unit tests of commands.py. import sys import unittest -from pystache.commands import main +from pystache.commands.render import main ORIGINAL_STDOUT = sys.stdout -- cgit v1.2.1 From 0db070487943ad43bed2122ab57283f8a479799d Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 14 Apr 2012 10:26:21 -0700 Subject: Stubbed out a stand-alone script command. The eventual goal is to have a test script that behaves well with tox. Using a stand-alone test script that we control instead of an out-of-the-box command-line script like nosetests or Distribute's test should give us more flexibility in dealing with any idiosyncracies that occur with tox. --- pystache/commands/test.py | 24 ++++++++++++++++++++++++ pystache/tests/test_locator.py | 15 +++++++++++---- pystache/tests/test_specloader.py | 21 +++++++++++++-------- 3 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 pystache/commands/test.py (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py new file mode 100644 index 0000000..f8afb96 --- /dev/null +++ b/pystache/commands/test.py @@ -0,0 +1,24 @@ +# coding: utf-8 + +""" +This module provides a command to test pystache (unit tests, doctests, etc). + +""" + +import sys + +# TODO: make nose unnecessary. +import nose + +import pystache + + +def main(sys_argv): + # This does not work with the --with-doctest flag yet because of the + # following issue: + # https://github.com/nose-devs/nose/issues/383 + nose.main(module=pystache) + + +if __name__=='__main__': + main(sys.argv) diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py index ddebef8..d11c192 100644 --- a/pystache/tests/test_locator.py +++ b/pystache/tests/test_locator.py @@ -34,13 +34,20 @@ class LocatorTests(unittest.TestCase): locator = Locator(extension=False) self.assertTrue(locator.template_extension is False) + def _assert_paths(self, actual, expected): + """ + Assert that two paths are the same. + + """ + self.assertEqual(actual, expected) + def test_get_object_directory(self): locator = Locator() obj = SayHello() actual = locator.get_object_directory(obj) - self.assertEqual(actual, os.path.abspath(DATA_DIR)) + self._assert_paths(actual, DATA_DIR) def test_get_object_directory__not_hasattr_module(self): locator = Locator() @@ -111,9 +118,9 @@ class LocatorTests(unittest.TestCase): obj = SayHello() actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache') - expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) + expected = os.path.join(DATA_DIR, 'sample_view.mustache') - self.assertEqual(actual, expected) + self._assert_paths(actual, expected) def test_find_object__none_file_name(self): locator = Locator() @@ -121,7 +128,7 @@ class LocatorTests(unittest.TestCase): obj = SayHello() actual = locator.find_object(search_dirs=[], obj=obj) - expected = os.path.abspath(os.path.join(DATA_DIR, 'say_hello.mustache')) + expected = os.path.join(DATA_DIR, 'say_hello.mustache') self.assertEqual(actual, expected) diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py index ac32dd6..6fc2256 100644 --- a/pystache/tests/test_specloader.py +++ b/pystache/tests/test_specloader.py @@ -19,10 +19,8 @@ from pystache import TemplateSpec from pystache.locator import Locator from pystache.loader import Loader from pystache.specloader import SpecLoader -from pystache.tests.common import DATA_DIR -from pystache.tests.common import EXAMPLES_DIR -from pystache.tests.common import AssertIsMixin -from pystache.tests.common import AssertStringMixin +from pystache.tests.common import DATA_DIR, EXAMPLES_DIR +from pystache.tests.common import AssertIsMixin, AssertStringMixin from pystache.tests.data.views import SampleView from pystache.tests.data.views import NonAscii @@ -358,6 +356,13 @@ class TemplateSpecTests(unittest.TestCase): view.template_extension = 'txt' self._assert_template_location(view, (None, 'sample_view.txt')) + def _assert_paths(self, actual, expected): + """ + Assert that two paths are the same. + + """ + self.assertEqual(actual, expected) + def test_find__with_directory(self): """ Test _find() with a view that has a directory specified. @@ -370,9 +375,9 @@ class TemplateSpecTests(unittest.TestCase): self.assertTrue(loader._find_relative(view)[0] is not None) actual = loader._find(view) - expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) + expected = os.path.join(DATA_DIR, 'foo/bar.txt') - self.assertEqual(actual, expected) + self._assert_paths(actual, expected) def test_find__without_directory(self): """ @@ -385,9 +390,9 @@ class TemplateSpecTests(unittest.TestCase): self.assertTrue(loader._find_relative(view)[0] is None) actual = loader._find(view) - expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) + expected = os.path.join(DATA_DIR, 'sample_view.mustache') - self.assertEqual(actual, expected) + self._assert_paths(actual, expected) def _assert_get_template(self, custom, expected): loader = self._make_loader() -- cgit v1.2.1 From 9067814cbb2ce7e70bbfe51d4828691c9970ac89 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 14 Apr 2012 11:46:15 -0700 Subject: Made the argument to pystache.command.render's main() function optional. --- pystache/commands/render.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/render.py b/pystache/commands/render.py index e9fe490..cfe1652 100644 --- a/pystache/commands/render.py +++ b/pystache/commands/render.py @@ -54,7 +54,12 @@ def parse_args(sys_argv, usage): return template, context -def main(sys_argv): +# TODO: verify whether the setup() method's entry_points argument +# supports passing arguments to main: +# +# http://packages.python.org/distribute/setuptools.html#automatic-script-creation +# +def main(sys_argv=sys.argv): template, context = parse_args(sys_argv, USAGE) if template.endswith('.mustache'): @@ -77,4 +82,4 @@ def main(sys_argv): if __name__=='__main__': - main(sys.argv) + main() -- cgit v1.2.1 From be685f389225be0bd6a4042996232409e4d71d48 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 15 Apr 2012 00:26:39 -0700 Subject: Moved examples/ to pystache/tests/examples/. --- pystache/tests/examples/__init__.py | 0 pystache/tests/examples/comments.mustache | 1 + pystache/tests/examples/comments.py | 4 +++ pystache/tests/examples/complex.mustache | 6 ++++ pystache/tests/examples/complex.py | 20 ++++++++++++++ pystache/tests/examples/delimiters.mustache | 6 ++++ pystache/tests/examples/delimiters.py | 10 +++++++ pystache/tests/examples/double_section.mustache | 3 ++ pystache/tests/examples/double_section.py | 7 +++++ pystache/tests/examples/escaped.mustache | 1 + pystache/tests/examples/escaped.py | 4 +++ pystache/tests/examples/extensionless | 1 + pystache/tests/examples/inner_partial.mustache | 1 + pystache/tests/examples/inner_partial.txt | 1 + pystache/tests/examples/inverted.mustache | 1 + pystache/tests/examples/inverted.py | 27 ++++++++++++++++++ pystache/tests/examples/lambdas.mustache | 1 + pystache/tests/examples/lambdas.py | 32 ++++++++++++++++++++++ pystache/tests/examples/looping_partial.mustache | 1 + pystache/tests/examples/nested_context.mustache | 1 + pystache/tests/examples/nested_context.py | 26 ++++++++++++++++++ .../tests/examples/partial_in_partial.mustache | 1 + .../tests/examples/partial_with_lambda.mustache | 1 + .../partial_with_partial_and_lambda.mustache | 1 + pystache/tests/examples/partials_with_lambdas.py | 6 ++++ pystache/tests/examples/readme.py | 3 ++ pystache/tests/examples/say_hello.mustache | 1 + pystache/tests/examples/simple.mustache | 1 + pystache/tests/examples/simple.py | 9 ++++++ pystache/tests/examples/tagless.mustache | 1 + pystache/tests/examples/template_partial.mustache | 2 ++ pystache/tests/examples/template_partial.py | 21 ++++++++++++++ pystache/tests/examples/template_partial.txt | 4 +++ pystache/tests/examples/unescaped.mustache | 1 + pystache/tests/examples/unescaped.py | 4 +++ pystache/tests/examples/unicode_input.mustache | 1 + pystache/tests/examples/unicode_input.py | 8 ++++++ pystache/tests/examples/unicode_output.mustache | 1 + pystache/tests/examples/unicode_output.py | 6 ++++ 39 files changed, 226 insertions(+) create mode 100644 pystache/tests/examples/__init__.py create mode 100644 pystache/tests/examples/comments.mustache create mode 100644 pystache/tests/examples/comments.py create mode 100644 pystache/tests/examples/complex.mustache create mode 100644 pystache/tests/examples/complex.py create mode 100644 pystache/tests/examples/delimiters.mustache create mode 100644 pystache/tests/examples/delimiters.py create mode 100644 pystache/tests/examples/double_section.mustache create mode 100644 pystache/tests/examples/double_section.py create mode 100644 pystache/tests/examples/escaped.mustache create mode 100644 pystache/tests/examples/escaped.py create mode 100644 pystache/tests/examples/extensionless create mode 100644 pystache/tests/examples/inner_partial.mustache create mode 100644 pystache/tests/examples/inner_partial.txt create mode 100644 pystache/tests/examples/inverted.mustache create mode 100644 pystache/tests/examples/inverted.py create mode 100644 pystache/tests/examples/lambdas.mustache create mode 100644 pystache/tests/examples/lambdas.py create mode 100644 pystache/tests/examples/looping_partial.mustache create mode 100644 pystache/tests/examples/nested_context.mustache create mode 100644 pystache/tests/examples/nested_context.py create mode 100644 pystache/tests/examples/partial_in_partial.mustache create mode 100644 pystache/tests/examples/partial_with_lambda.mustache create mode 100644 pystache/tests/examples/partial_with_partial_and_lambda.mustache create mode 100644 pystache/tests/examples/partials_with_lambdas.py create mode 100644 pystache/tests/examples/readme.py create mode 100644 pystache/tests/examples/say_hello.mustache create mode 100644 pystache/tests/examples/simple.mustache create mode 100644 pystache/tests/examples/simple.py create mode 100644 pystache/tests/examples/tagless.mustache create mode 100644 pystache/tests/examples/template_partial.mustache create mode 100644 pystache/tests/examples/template_partial.py create mode 100644 pystache/tests/examples/template_partial.txt create mode 100644 pystache/tests/examples/unescaped.mustache create mode 100644 pystache/tests/examples/unescaped.py create mode 100644 pystache/tests/examples/unicode_input.mustache create mode 100644 pystache/tests/examples/unicode_input.py create mode 100644 pystache/tests/examples/unicode_output.mustache create mode 100644 pystache/tests/examples/unicode_output.py (limited to 'pystache') diff --git a/pystache/tests/examples/__init__.py b/pystache/tests/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pystache/tests/examples/comments.mustache b/pystache/tests/examples/comments.mustache new file mode 100644 index 0000000..2a2a08b --- /dev/null +++ b/pystache/tests/examples/comments.mustache @@ -0,0 +1 @@ +

{{title}}{{! just something interesting... #or not... }}

\ No newline at end of file diff --git a/pystache/tests/examples/comments.py b/pystache/tests/examples/comments.py new file mode 100644 index 0000000..f9c3125 --- /dev/null +++ b/pystache/tests/examples/comments.py @@ -0,0 +1,4 @@ +class Comments(object): + + def title(self): + return "A Comedy of Errors" diff --git a/pystache/tests/examples/complex.mustache b/pystache/tests/examples/complex.mustache new file mode 100644 index 0000000..6de758b --- /dev/null +++ b/pystache/tests/examples/complex.mustache @@ -0,0 +1,6 @@ +

{{ header }}

+{{#list}} +
    +{{#item}}{{# current }}
  • {{name}}
  • +{{/ current }}{{#link}}
  • {{name}}
  • +{{/link}}{{/item}}
{{/list}}{{#empty}}

The list is empty.

{{/empty}} \ No newline at end of file diff --git a/pystache/tests/examples/complex.py b/pystache/tests/examples/complex.py new file mode 100644 index 0000000..e3f1767 --- /dev/null +++ b/pystache/tests/examples/complex.py @@ -0,0 +1,20 @@ +class Complex(object): + + def header(self): + return "Colors" + + def item(self): + items = [] + items.append({ 'name': 'red', 'current': True, 'url': '#Red' }) + items.append({ 'name': 'green', 'link': True, 'url': '#Green' }) + items.append({ 'name': 'blue', 'link': True, 'url': '#Blue' }) + return items + + def list(self): + return not self.empty() + + def empty(self): + return len(self.item()) == 0 + + def empty_list(self): + return []; diff --git a/pystache/tests/examples/delimiters.mustache b/pystache/tests/examples/delimiters.mustache new file mode 100644 index 0000000..92bea6d --- /dev/null +++ b/pystache/tests/examples/delimiters.mustache @@ -0,0 +1,6 @@ +{{=<% %>=}} +* <% first %> +<%=| |=%> +* | second | +|={{ }}=| +* {{ third }} diff --git a/pystache/tests/examples/delimiters.py b/pystache/tests/examples/delimiters.py new file mode 100644 index 0000000..a132ed0 --- /dev/null +++ b/pystache/tests/examples/delimiters.py @@ -0,0 +1,10 @@ +class Delimiters(object): + + def first(self): + return "It worked the first time." + + def second(self): + return "And it worked the second time." + + def third(self): + return "Then, surprisingly, it worked the third time." diff --git a/pystache/tests/examples/double_section.mustache b/pystache/tests/examples/double_section.mustache new file mode 100644 index 0000000..61f1917 --- /dev/null +++ b/pystache/tests/examples/double_section.mustache @@ -0,0 +1,3 @@ +{{#t}}* first{{/t}} +* {{two}} +{{#t}}* third{{/t}} \ No newline at end of file diff --git a/pystache/tests/examples/double_section.py b/pystache/tests/examples/double_section.py new file mode 100644 index 0000000..0bec602 --- /dev/null +++ b/pystache/tests/examples/double_section.py @@ -0,0 +1,7 @@ +class DoubleSection(object): + + def t(self): + return True + + def two(self): + return "second" diff --git a/pystache/tests/examples/escaped.mustache b/pystache/tests/examples/escaped.mustache new file mode 100644 index 0000000..8be4ccb --- /dev/null +++ b/pystache/tests/examples/escaped.mustache @@ -0,0 +1 @@ +

{{title}}

\ No newline at end of file diff --git a/pystache/tests/examples/escaped.py b/pystache/tests/examples/escaped.py new file mode 100644 index 0000000..fed1705 --- /dev/null +++ b/pystache/tests/examples/escaped.py @@ -0,0 +1,4 @@ +class Escaped(object): + + def title(self): + return "Bear > Shark" diff --git a/pystache/tests/examples/extensionless b/pystache/tests/examples/extensionless new file mode 100644 index 0000000..452c9fe --- /dev/null +++ b/pystache/tests/examples/extensionless @@ -0,0 +1 @@ +No file extension: {{foo}} \ No newline at end of file diff --git a/pystache/tests/examples/inner_partial.mustache b/pystache/tests/examples/inner_partial.mustache new file mode 100644 index 0000000..2863764 --- /dev/null +++ b/pystache/tests/examples/inner_partial.mustache @@ -0,0 +1 @@ +Again, {{title}}! \ No newline at end of file diff --git a/pystache/tests/examples/inner_partial.txt b/pystache/tests/examples/inner_partial.txt new file mode 100644 index 0000000..650c959 --- /dev/null +++ b/pystache/tests/examples/inner_partial.txt @@ -0,0 +1 @@ +## Again, {{title}}! ## \ No newline at end of file diff --git a/pystache/tests/examples/inverted.mustache b/pystache/tests/examples/inverted.mustache new file mode 100644 index 0000000..fbea98d --- /dev/null +++ b/pystache/tests/examples/inverted.mustache @@ -0,0 +1 @@ +{{^f}}one{{/f}}, {{ two }}, {{^f}}three{{/f}}{{^t}}, four!{{/t}}{{^empty_list}}, empty list{{/empty_list}}{{^populated_list}}, shouldn't see me{{/populated_list}} \ No newline at end of file diff --git a/pystache/tests/examples/inverted.py b/pystache/tests/examples/inverted.py new file mode 100644 index 0000000..2a05302 --- /dev/null +++ b/pystache/tests/examples/inverted.py @@ -0,0 +1,27 @@ +from pystache import TemplateSpec + +class Inverted(object): + + def t(self): + return True + + def f(self): + return False + + def two(self): + return 'two' + + def empty_list(self): + return [] + + def populated_list(self): + return ['some_value'] + +class InvertedLists(Inverted, TemplateSpec): + template_name = 'inverted' + + def t(self): + return [0, 1, 2] + + def f(self): + return [] diff --git a/pystache/tests/examples/lambdas.mustache b/pystache/tests/examples/lambdas.mustache new file mode 100644 index 0000000..9dffca5 --- /dev/null +++ b/pystache/tests/examples/lambdas.mustache @@ -0,0 +1 @@ +{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}} \ No newline at end of file diff --git a/pystache/tests/examples/lambdas.py b/pystache/tests/examples/lambdas.py new file mode 100644 index 0000000..653531d --- /dev/null +++ b/pystache/tests/examples/lambdas.py @@ -0,0 +1,32 @@ +from pystache import TemplateSpec + +def rot(s, n=13): + r = "" + for c in s: + cc = c + if cc.isalpha(): + cc = cc.lower() + o = ord(cc) + ro = (o+n) % 122 + if ro == 0: ro = 122 + if ro < 97: ro += 96 + cc = chr(ro) + r = ''.join((r,cc)) + return r + +def replace(subject, this='foo', with_this='bar'): + return subject.replace(this, with_this) + + +# This class subclasses TemplateSpec because at least one unit test +# sets the template attribute. +class Lambdas(TemplateSpec): + + def replace_foo_with_bar(self, text=None): + return replace + + def rot13(self, text=None): + return rot + + def sort(self, text=None): + return lambda text: ''.join(sorted(text)) diff --git a/pystache/tests/examples/looping_partial.mustache b/pystache/tests/examples/looping_partial.mustache new file mode 100644 index 0000000..577f736 --- /dev/null +++ b/pystache/tests/examples/looping_partial.mustache @@ -0,0 +1 @@ +Looping partial {{item}}! \ No newline at end of file diff --git a/pystache/tests/examples/nested_context.mustache b/pystache/tests/examples/nested_context.mustache new file mode 100644 index 0000000..ce570d6 --- /dev/null +++ b/pystache/tests/examples/nested_context.mustache @@ -0,0 +1 @@ +{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}} \ No newline at end of file diff --git a/pystache/tests/examples/nested_context.py b/pystache/tests/examples/nested_context.py new file mode 100644 index 0000000..4626ac0 --- /dev/null +++ b/pystache/tests/examples/nested_context.py @@ -0,0 +1,26 @@ +from pystache import TemplateSpec + +class NestedContext(TemplateSpec): + + def __init__(self, renderer): + self.renderer = renderer + + def _context_get(self, key): + return self.renderer.context.get(key) + + def outer_thing(self): + return "two" + + def foo(self): + return {'thing1': 'one', 'thing2': 'foo'} + + def derp(self): + return [{'inner': 'car'}] + + def herp(self): + return [{'outer': 'car'}] + + def nested_context_in_view(self): + if self._context_get('outer') == self._context_get('inner'): + return 'it works!' + return '' diff --git a/pystache/tests/examples/partial_in_partial.mustache b/pystache/tests/examples/partial_in_partial.mustache new file mode 100644 index 0000000..c61ceb1 --- /dev/null +++ b/pystache/tests/examples/partial_in_partial.mustache @@ -0,0 +1 @@ +{{>simple}} \ No newline at end of file diff --git a/pystache/tests/examples/partial_with_lambda.mustache b/pystache/tests/examples/partial_with_lambda.mustache new file mode 100644 index 0000000..2989f56 --- /dev/null +++ b/pystache/tests/examples/partial_with_lambda.mustache @@ -0,0 +1 @@ +{{#rot13}}abcdefghijklm{{/rot13}} \ No newline at end of file diff --git a/pystache/tests/examples/partial_with_partial_and_lambda.mustache b/pystache/tests/examples/partial_with_partial_and_lambda.mustache new file mode 100644 index 0000000..0729e10 --- /dev/null +++ b/pystache/tests/examples/partial_with_partial_and_lambda.mustache @@ -0,0 +1 @@ +{{>partial_with_lambda}}{{#rot13}}abcdefghijklm{{/rot13}} \ No newline at end of file diff --git a/pystache/tests/examples/partials_with_lambdas.py b/pystache/tests/examples/partials_with_lambdas.py new file mode 100644 index 0000000..42d1b24 --- /dev/null +++ b/pystache/tests/examples/partials_with_lambdas.py @@ -0,0 +1,6 @@ +from pystache.tests.examples.lambdas import rot + +class PartialsWithLambdas(object): + + def rot(self): + return rot diff --git a/pystache/tests/examples/readme.py b/pystache/tests/examples/readme.py new file mode 100644 index 0000000..23b44f5 --- /dev/null +++ b/pystache/tests/examples/readme.py @@ -0,0 +1,3 @@ +class SayHello(object): + def to(self): + return "Pizza" diff --git a/pystache/tests/examples/say_hello.mustache b/pystache/tests/examples/say_hello.mustache new file mode 100644 index 0000000..7d8dfea --- /dev/null +++ b/pystache/tests/examples/say_hello.mustache @@ -0,0 +1 @@ +Hello, {{to}}! \ No newline at end of file diff --git a/pystache/tests/examples/simple.mustache b/pystache/tests/examples/simple.mustache new file mode 100644 index 0000000..9214dab --- /dev/null +++ b/pystache/tests/examples/simple.mustache @@ -0,0 +1 @@ +Hi {{thing}}!{{blank}} \ No newline at end of file diff --git a/pystache/tests/examples/simple.py b/pystache/tests/examples/simple.py new file mode 100644 index 0000000..3252a81 --- /dev/null +++ b/pystache/tests/examples/simple.py @@ -0,0 +1,9 @@ +from pystache import TemplateSpec + +class Simple(TemplateSpec): + + def thing(self): + return "pizza" + + def blank(self): + pass diff --git a/pystache/tests/examples/tagless.mustache b/pystache/tests/examples/tagless.mustache new file mode 100644 index 0000000..ad4dd31 --- /dev/null +++ b/pystache/tests/examples/tagless.mustache @@ -0,0 +1 @@ +No tags... \ No newline at end of file diff --git a/pystache/tests/examples/template_partial.mustache b/pystache/tests/examples/template_partial.mustache new file mode 100644 index 0000000..03f76cf --- /dev/null +++ b/pystache/tests/examples/template_partial.mustache @@ -0,0 +1,2 @@ +

{{title}}

+{{>inner_partial}} \ No newline at end of file diff --git a/pystache/tests/examples/template_partial.py b/pystache/tests/examples/template_partial.py new file mode 100644 index 0000000..a9052d7 --- /dev/null +++ b/pystache/tests/examples/template_partial.py @@ -0,0 +1,21 @@ +from pystache import TemplateSpec + +class TemplatePartial(TemplateSpec): + + def __init__(self, renderer): + self.renderer = renderer + + def _context_get(self, key): + return self.renderer.context.get(key) + + def title(self): + return "Welcome" + + def title_bars(self): + return '-' * len(self.title()) + + def looping(self): + return [{'item': 'one'}, {'item': 'two'}, {'item': 'three'}] + + def thing(self): + return self._context_get('prop') diff --git a/pystache/tests/examples/template_partial.txt b/pystache/tests/examples/template_partial.txt new file mode 100644 index 0000000..d9b5f6e --- /dev/null +++ b/pystache/tests/examples/template_partial.txt @@ -0,0 +1,4 @@ +{{title}} +{{title_bars}} + +{{>inner_partial}} diff --git a/pystache/tests/examples/unescaped.mustache b/pystache/tests/examples/unescaped.mustache new file mode 100644 index 0000000..9982708 --- /dev/null +++ b/pystache/tests/examples/unescaped.mustache @@ -0,0 +1 @@ +

{{{title}}}

\ No newline at end of file diff --git a/pystache/tests/examples/unescaped.py b/pystache/tests/examples/unescaped.py new file mode 100644 index 0000000..67c12ca --- /dev/null +++ b/pystache/tests/examples/unescaped.py @@ -0,0 +1,4 @@ +class Unescaped(object): + + def title(self): + return "Bear > Shark" diff --git a/pystache/tests/examples/unicode_input.mustache b/pystache/tests/examples/unicode_input.mustache new file mode 100644 index 0000000..f654cd1 --- /dev/null +++ b/pystache/tests/examples/unicode_input.mustache @@ -0,0 +1 @@ +abcdé \ No newline at end of file diff --git a/pystache/tests/examples/unicode_input.py b/pystache/tests/examples/unicode_input.py new file mode 100644 index 0000000..2c10fcb --- /dev/null +++ b/pystache/tests/examples/unicode_input.py @@ -0,0 +1,8 @@ +from pystache import TemplateSpec + +class UnicodeInput(TemplateSpec): + + template_encoding = 'utf8' + + def age(self): + return 156 diff --git a/pystache/tests/examples/unicode_output.mustache b/pystache/tests/examples/unicode_output.mustache new file mode 100644 index 0000000..8495f56 --- /dev/null +++ b/pystache/tests/examples/unicode_output.mustache @@ -0,0 +1 @@ +

Name: {{name}}

\ No newline at end of file diff --git a/pystache/tests/examples/unicode_output.py b/pystache/tests/examples/unicode_output.py new file mode 100644 index 0000000..d5579c3 --- /dev/null +++ b/pystache/tests/examples/unicode_output.py @@ -0,0 +1,6 @@ +# encoding: utf-8 + +class UnicodeOutput(object): + + def name(self): + return u'Henri Poincaré' -- cgit v1.2.1 From ee07ff5a8e14939ce9fc87dfe6439a2ed045b76e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 15 Apr 2012 00:29:25 -0700 Subject: Some updates to the test code for moving the examples folder. --- pystache/tests/common.py | 3 +-- pystache/tests/test_locator.py | 6 +++--- pystache/tests/test_specloader.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 8df6379..aba1905 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -7,10 +7,9 @@ Provides test-related code that can be used by all tests. import os -import examples import pystache from pystache import defaults - +from pystache.tests import examples # Save a reference to the original function to avoid recursion. _DEFAULT_TAG_ESCAPE = defaults.TAG_ESCAPE diff --git a/pystache/tests/test_locator.py b/pystache/tests/test_locator.py index d11c192..3a8b229 100644 --- a/pystache/tests/test_locator.py +++ b/pystache/tests/test_locator.py @@ -14,7 +14,7 @@ import unittest from pystache.loader import Loader as Reader from pystache.locator import Locator -from pystache.tests.common import DATA_DIR +from pystache.tests.common import DATA_DIR, EXAMPLES_DIR from pystache.tests.data.views import SayHello @@ -78,13 +78,13 @@ class LocatorTests(unittest.TestCase): def test_find_name(self): locator = Locator() - path = locator.find_name(search_dirs=['examples'], template_name='simple') + path = locator.find_name(search_dirs=[EXAMPLES_DIR], template_name='simple') self.assertEqual(os.path.basename(path), 'simple.mustache') def test_find_name__using_list_of_paths(self): locator = Locator() - path = locator.find_name(search_dirs=['doesnt_exist', 'examples'], template_name='simple') + path = locator.find_name(search_dirs=[EXAMPLES_DIR, 'doesnt_exist'], template_name='simple') self.assertTrue(path) diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py index 6fc2256..8332b28 100644 --- a/pystache/tests/test_specloader.py +++ b/pystache/tests/test_specloader.py @@ -45,7 +45,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): self.assertRaises(IOError, renderer.render, view) # TODO: change this test to remove the following brittle line. - view.template_rel_directory = "../../examples" + view.template_rel_directory = "examples" actual = renderer.render(view) self.assertEqual(actual, "No tags...") -- cgit v1.2.1 From 4ac89345fdbf3e3fe45d6c4356a800a22fe266d0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 15 Apr 2012 15:49:21 -0700 Subject: Added a TODO to pystache/commands/test.py about nose's defaultTest keyword argument. --- pystache/commands/test.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py index f8afb96..8588002 100644 --- a/pystache/commands/test.py +++ b/pystache/commands/test.py @@ -17,6 +17,8 @@ def main(sys_argv): # This does not work with the --with-doctest flag yet because of the # following issue: # https://github.com/nose-devs/nose/issues/383 + # TODO: change module keyword argument to defaultTest keyword argument: + # http://readthedocs.org/docs/nose/en/latest/api/core.html#module-nose.core nose.main(module=pystache) -- cgit v1.2.1 From 567c41443e8fb46da0dea257600429f0711b023b Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 15 Apr 2012 17:24:57 -0700 Subject: Added a TODO to pystache/__init__.py about "from pystache import *". --- pystache/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index c1070e3..1bf7173 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -1,2 +1,6 @@ # We keep all initialization code in a separate module. from pystache.init import * + +# TODO: make sure that "from pystache import *" exposes only the following: +# ['__version__', 'render', 'Renderer', 'TemplateSpec'] +# and add a unit test for this. -- cgit v1.2.1 From 2a6ed788455db12fe9a103116b33dbd39240622e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 16 Apr 2012 08:08:19 -0500 Subject: Added a TODO to the package __init__ about importing from pystache.init. --- pystache/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index 1bf7173..b7468e0 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -1,4 +1,6 @@ # We keep all initialization code in a separate module. +# TODO: consider doing something like this instead: +# from pystache.init import __version__, render, Renderer, TemplateSpec from pystache.init import * # TODO: make sure that "from pystache import *" exposes only the following: -- cgit v1.2.1 From 35202296d13f01e6a45e44abe4a5f6305506588c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 16 Apr 2012 20:37:39 -0500 Subject: Added a Tester class to do test discovery without using nose. --- pystache/tests/main.py | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 pystache/tests/main.py (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py new file mode 100644 index 0000000..a47957e --- /dev/null +++ b/pystache/tests/main.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" +Allows all tests to be run. + +This module is for our test console script. + +""" + +import os +import sys +import unittest + + +UNITTEST_FILE_PREFIX = "test_" + +# TODO: enhance this to work with doctests (instead of using the load_tests +# protocol), etc. +class Tester(object): + + """ + Discovers and runs unit tests. + + """ + + def _find_unittest_files(self, package_dir): + """ + Return a list of paths to all unit-test files in the given package directory. + + """ + unittest_paths = [] # Return value. + + def is_unittest(file_name): + return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') + + # os.walk() is new in Python 2.3 + # http://docs.python.org/library/os.html#os.walk + for dir_path, dir_names, file_names in os.walk(package_dir): + file_names = filter(is_unittest, file_names) + + for file_name in file_names: + unittest_path = os.path.join(dir_path, file_name) + unittest_paths.append(unittest_path) + + return unittest_paths + + def _modules_from_paths(self, package_dir, paths): + """ + Return a list of fully-qualified module names given paths. + + """ + package_dir = os.path.abspath(package_dir) + package_name = os.path.split(package_dir)[1] + + prefix_length = len(package_dir) + + module_names = [] + for path in paths: + path = os.path.abspath(path) # for example /subpackage/module.py + rel_path = path[prefix_length:] # for example /subpackage/module.py + rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module + + parts = [] + while True: + (rel_path, tail) = os.path.split(rel_path) + if not tail: + break + parts.insert(0, tail) + # We now have, for example, ['subpackage', 'module']. + parts.insert(0, package_name) + module = ".".join(parts) + module_names.append(module) + + return module_names + + def run_tests(self, package, sys_argv): + """ + Run all unit tests inside the given package. + + Arguments: + + package: a module instance corresponding to the package. + + sys_argv: a reference to sys.argv. + + """ + if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"): + # Then explicit modules or test names were provided, which + # the unittest module is equipped to handle. + unittest.main(argv=sys_argv, module=None) + # No need to return since unitttest.main() exits. + + # Otherwise, auto-detect all unit tests. + + package_dir = os.path.dirname(package.__file__) + unittest_paths = self._find_unittest_files(package_dir) + + modules = self._modules_from_paths(package_dir, unittest_paths) + modules.sort() + + # This is a sanity check to ensure that the unit-test discovery + # methods are working. + if len(modules) < 1: + raise Exception("No unit-test modules found.") + + sys_argv.extend(modules) + + # We pass None for the module because we do not want the unittest + # module to resolve module names relative to a given module. + # (This would require importing all of the unittest modules from + # this module.) See the loadTestsFromName() method of the + # unittest.TestLoader class for more details on this parameter. + unittest.main(argv=sys_argv, module=None) -- cgit v1.2.1 From f14f141d9ce4a14be98682bca0a42cbbe9175e01 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 16 Apr 2012 20:38:56 -0500 Subject: Added a TODO to pystache/context.py to use EAFP instead of a global variable. --- pystache/context.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'pystache') diff --git a/pystache/context.py b/pystache/context.py index d009fdf..a8f3964 100644 --- a/pystache/context.py +++ b/pystache/context.py @@ -13,6 +13,10 @@ _BUILTIN_MODULE = type(0).__module__ # not being found on lookup. This lets us distinguish between the case # of a key's value being None with the case of a key not being found -- # without having to rely on exceptions (e.g. KeyError) for flow control. +# +# TODO: eliminate the need for a private global variable, e.g. by using the +# preferred Python approach of "easier to ask for forgiveness than permission": +# http://docs.python.org/glossary.html#term-eafp class NotFound(object): pass _NOT_FOUND = NotFound() -- cgit v1.2.1 From 224d2a1d31d912d8ecc469e92ae13871afadac53 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 16 Apr 2012 20:49:12 -0500 Subject: Added a TODO to pystache/tests/main.py. --- pystache/tests/main.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index a47957e..68b41a1 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -73,6 +73,7 @@ class Tester(object): return module_names + # TODO: consider replacing the package argument with a package_dir argument. def run_tests(self, package, sys_argv): """ Run all unit tests inside the given package. -- cgit v1.2.1 From 328b018b9ac5bd6695a477d29989d0946881dcd7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 16 Apr 2012 20:50:23 -0500 Subject: Updated pystache/commands/test.py to use the new test-discovery code. --- pystache/commands/test.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py index 8588002..4bf0a4b 100644 --- a/pystache/commands/test.py +++ b/pystache/commands/test.py @@ -7,20 +7,14 @@ This module provides a command to test pystache (unit tests, doctests, etc). import sys -# TODO: make nose unnecessary. -import nose - import pystache +from pystache.tests.main import Tester -def main(sys_argv): - # This does not work with the --with-doctest flag yet because of the - # following issue: - # https://github.com/nose-devs/nose/issues/383 - # TODO: change module keyword argument to defaultTest keyword argument: - # http://readthedocs.org/docs/nose/en/latest/api/core.html#module-nose.core - nose.main(module=pystache) +def main(sys_argv=sys.argv): + tester = Tester() + tester.run_tests(package=pystache, sys_argv=sys_argv) if __name__=='__main__': - main(sys.argv) + main() -- cgit v1.2.1 From 5a47ea6dd89acb51fee1e433435ccc28858e51e4 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 13:18:03 -0700 Subject: Added TODO notes to pystache/tests/main.py. --- pystache/tests/main.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 68b41a1..b9bc58f 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -16,6 +16,21 @@ UNITTEST_FILE_PREFIX = "test_" # TODO: enhance this to work with doctests (instead of using the load_tests # protocol), etc. + +# Notes for TODO: +# +# The function unittest.main() is an alias for unittest.TestProgram's +# constructor. The constructor calls self.runTests() as its final step, which +# expects self.test to be set. The constructor sets the self.test attribute +# by calling one of self.testLoader's "loadTests" methods. These methods +# return a unittest.TestSuite instance. Thus, self.test is set to a TestSuite +# instance prior to calling runTests(). +# +# Our strategy is to subclass unittest.TestProgram and override its runTests() +# method. Our implementation of runTests() will add to self.test additional +# TestCase or TestSuite instances (e.g. doctests and spec tests), and then +# call the base class's runTests(). + class Tester(object): """ -- cgit v1.2.1 From d9e8496005c5ac208b33fd1e1df672442f17f2ca Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 13:29:56 -0700 Subject: Started refactoring pystache/tests/main.py and stubbed out _PystacheTestProgram. --- pystache/tests/main.py | 111 ++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 48 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index b9bc58f..e9504f0 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -10,6 +10,7 @@ This module is for our test console script. import os import sys import unittest +from unittest import TestProgram UNITTEST_FILE_PREFIX = "test_" @@ -31,62 +32,79 @@ UNITTEST_FILE_PREFIX = "test_" # TestCase or TestSuite instances (e.g. doctests and spec tests), and then # call the base class's runTests(). -class Tester(object): +def _find_unittest_files(package_dir): + """ + Return a list of paths to all unit-test files in the given package directory. """ - Discovers and runs unit tests. + paths = [] # Return value. + + def is_unittest(file_name): + return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') + + # os.walk() is new in Python 2.3 + # http://docs.python.org/library/os.html#os.walk + for dir_path, dir_names, file_names in os.walk(package_dir): + file_names = filter(is_unittest, file_names) + + for file_name in file_names: + path = os.path.join(dir_path, file_name) + paths.append(path) + + return paths + +def _get_module_names(package_dir, paths): """ + Return a list of fully-qualified test module names given a list of module paths. - def _find_unittest_files(self, package_dir): - """ - Return a list of paths to all unit-test files in the given package directory. + """ + package_dir = os.path.abspath(package_dir) + package_name = os.path.split(package_dir)[1] - """ - unittest_paths = [] # Return value. + prefix_length = len(package_dir) - def is_unittest(file_name): - return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') + module_names = [] + for path in paths: + path = os.path.abspath(path) # for example /subpackage/module.py + rel_path = path[prefix_length:] # for example /subpackage/module.py + rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module - # os.walk() is new in Python 2.3 - # http://docs.python.org/library/os.html#os.walk - for dir_path, dir_names, file_names in os.walk(package_dir): - file_names = filter(is_unittest, file_names) + parts = [] + while True: + (rel_path, tail) = os.path.split(rel_path) + if not tail: + break + parts.insert(0, tail) + # We now have, for example, ['subpackage', 'module']. + parts.insert(0, package_name) + module = ".".join(parts) + module_names.append(module) - for file_name in file_names: - unittest_path = os.path.join(dir_path, file_name) - unittest_paths.append(unittest_path) + return module_names - return unittest_paths - def _modules_from_paths(self, package_dir, paths): - """ - Return a list of fully-qualified module names given paths. +def _get_test_module_names(package_dir): + """ + Return a list of fully-qualified module names given a list of module paths. - """ - package_dir = os.path.abspath(package_dir) - package_name = os.path.split(package_dir)[1] - - prefix_length = len(package_dir) - - module_names = [] - for path in paths: - path = os.path.abspath(path) # for example /subpackage/module.py - rel_path = path[prefix_length:] # for example /subpackage/module.py - rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module - - parts = [] - while True: - (rel_path, tail) = os.path.split(rel_path) - if not tail: - break - parts.insert(0, tail) - # We now have, for example, ['subpackage', 'module']. - parts.insert(0, package_name) - module = ".".join(parts) - module_names.append(module) - - return module_names + """ + paths = _find_unittest_files(package_dir) + modules = _get_module_names(package_dir, paths) + + return modules + + +class _PystacheTestProgram(TestProgram): + pass + + +class Tester(object): + + """ + Discovers and runs unit tests. + + """ # TODO: consider replacing the package argument with a package_dir argument. def run_tests(self, package, sys_argv): @@ -105,13 +123,10 @@ class Tester(object): # the unittest module is equipped to handle. unittest.main(argv=sys_argv, module=None) # No need to return since unitttest.main() exits. - # Otherwise, auto-detect all unit tests. package_dir = os.path.dirname(package.__file__) - unittest_paths = self._find_unittest_files(package_dir) - - modules = self._modules_from_paths(package_dir, unittest_paths) + modules = _get_test_module_names(package_dir) modules.sort() # This is a sanity check to ensure that the unit-test discovery -- cgit v1.2.1 From 8aa242e9d0b46ad1edff9d559413572b02c1efe6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 13:39:22 -0700 Subject: Finished refactoring pystache/tests/main.py prior to add doctest support, etc. --- pystache/tests/main.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index e9504f0..7c71fd4 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -94,6 +94,22 @@ def _get_test_module_names(package_dir): return modules +def _discover_test_modules(package): + """ + Discover and return a sorted list of the names of unit-test modules. + + """ + package_dir = os.path.dirname(package.__file__) + modules = _get_test_module_names(package_dir) + modules.sort() + + # This is a sanity check to ensure that the unit-test discovery + # methods are working. + if len(modules) < 1: + raise Exception("No unit-test modules found.") + + return modules + class _PystacheTestProgram(TestProgram): pass @@ -118,23 +134,11 @@ class Tester(object): sys_argv: a reference to sys.argv. """ - if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"): - # Then explicit modules or test names were provided, which - # the unittest module is equipped to handle. - unittest.main(argv=sys_argv, module=None) - # No need to return since unitttest.main() exits. - # Otherwise, auto-detect all unit tests. - - package_dir = os.path.dirname(package.__file__) - modules = _get_test_module_names(package_dir) - modules.sort() - - # This is a sanity check to ensure that the unit-test discovery - # methods are working. - if len(modules) < 1: - raise Exception("No unit-test modules found.") - - sys_argv.extend(modules) + if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): + # Then no explicit module or test names were provided, so + # auto-detect all unit tests. + module_names = _discover_test_modules(package) + sys_argv.extend(module_names) # We pass None for the module because we do not want the unittest # module to resolve module names relative to a given module. @@ -142,3 +146,4 @@ class Tester(object): # this module.) See the loadTestsFromName() method of the # unittest.TestLoader class for more details on this parameter. unittest.main(argv=sys_argv, module=None) + # No need to return since unitttest.main() exits. -- cgit v1.2.1 From bd207abff65f5908c444867558dd11c9b7bd199c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 13:45:24 -0700 Subject: Stubbed out _PystacheTestProgram.runTests(). --- pystache/tests/main.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 7c71fd4..0cd3e8e 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -9,7 +9,6 @@ This module is for our test console script. import os import sys -import unittest from unittest import TestProgram @@ -111,8 +110,17 @@ def _discover_test_modules(package): return modules + class _PystacheTestProgram(TestProgram): - pass + + """ + Instantiating an instance of this class runs all tests. + + """ + + def runTests(self): + # TODO: add doctests, etc. to the self.test TestSuite. + TestProgram.runTests(self) class Tester(object): @@ -145,5 +153,5 @@ class Tester(object): # (This would require importing all of the unittest modules from # this module.) See the loadTestsFromName() method of the # unittest.TestLoader class for more details on this parameter. - unittest.main(argv=sys_argv, module=None) + _PystacheTestProgram(argv=sys_argv, module=None) # No need to return since unitttest.main() exits. -- cgit v1.2.1 From 67d7762848b2af4b4f09928146732c5136882a89 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 20:20:58 -0700 Subject: Moved code from load_tests() to new doctesting module. --- pystache/tests/common.py | 1 + pystache/tests/doctesting.py | 78 +++++++++++++++++++++++++++++++++++++++++ pystache/tests/test_doctests.py | 63 ++------------------------------- 3 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 pystache/tests/doctesting.py (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index aba1905..6aa0213 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -17,6 +17,7 @@ _TESTS_DIR = os.path.dirname(pystache.tests.__file__) DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. EXAMPLES_DIR = os.path.dirname(examples.__file__) +# TODO: change SOURCE_DIR to PACKAGE_DIR. SOURCE_DIR = os.path.dirname(pystache.__file__) PROJECT_DIR = os.path.join(SOURCE_DIR, '..') SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py new file mode 100644 index 0000000..a0f116d --- /dev/null +++ b/pystache/tests/doctesting.py @@ -0,0 +1,78 @@ +# coding: utf-8 + +""" +Exposes a get_doctests() function for the project's test harness. + +""" + +import os +import pkgutil +import doctest +import traceback + + +from pystache.tests.common import PROJECT_DIR, SOURCE_DIR + + +# The paths to text files (i.e. non-module files) containing doctests. +# Paths should be OS-specific and relative to the project directory. +TEXT_DOCTEST_PATHS = ['README.rst'] + + + +def get_module_doctests(): + """ + Return a list of TestSuite instances for all doctests in the pacakqge. + + """ + suites = [] + + # Since module_relative is False in our calls to DocFileSuite below, + # paths should be OS-specific. Moreover, we choose absolute paths + # so that the current working directory does not come into play. + # See the following for more info-- + # + # http://docs.python.org/library/doctest.html#doctest.DocFileSuite + # + paths = [os.path.join(PROJECT_DIR, path) for path in TEXT_DOCTEST_PATHS] + for path in paths: + suite = doctest.DocFileSuite(path, module_relative=False) + suites.append(suite) + + modules = _get_module_doctests(SOURCE_DIR) + for module in modules: + suite = doctest.DocTestSuite(module) + suites.append(suite) + + return suites + + +def _get_module_doctests(package_dir): + modules = [] + + for pkg in pkgutil.walk_packages([package_dir]): + # The importer is a pkgutil.ImpImporter instance: + # + # http://docs.python.org/library/pkgutil.html#pkgutil.ImpImporter + # + importer, module_name, is_package = pkg + if is_package: + # Otherwise, we will get the following error when adding tests: + # + # ValueError: (, 'has no tests') + # + continue + # The loader is a pkgutil.ImpLoader instance. + loader = importer.find_module(module_name) + try: + module = loader.load_module(module_name) + except ImportError, e: + # In some situations, the test harness was swallowing and/or + # suppressing the display of the stack trace when errors + # occurred here. The following code makes errors occurring here + # easier to troubleshoot. + details = "".join(traceback.format_exception(*sys.exc_info())) + raise ImportError(details) + modules.append(module) + + return modules diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index bb7def8..5ff8b8a 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -10,19 +10,7 @@ Creates unittest.TestSuite instances for the doctests in the project. # http://docs.python.org/library/doctest.html#unittest-api # -import os -import doctest -import pkgutil -import traceback -import unittest - -import pystache -from pystache.tests.common import PROJECT_DIR, SOURCE_DIR - - -# The paths to text files (i.e. non-module files) containing doctests. -# Paths should be OS-specific and relative to the project directory. -text_file_paths = ['README.rst'] +from pystache.tests.doctesting import get_module_doctests # The following load_tests() function implements unittests's load_tests @@ -46,52 +34,7 @@ text_file_paths = ['README.rst'] # before version 2.7. # def load_tests(loader, tests, ignore): - # Since module_relative is False in our calls to DocFileSuite below, - # paths should be OS-specific. Moreover, we choose absolute paths - # so that the current working directory does not come into play. - # See the following for more info-- - # - # http://docs.python.org/library/doctest.html#doctest.DocFileSuite - # - paths = [os.path.join(PROJECT_DIR, path) for path in text_file_paths] - for path in paths: - suite = doctest.DocFileSuite(path, module_relative=False) - tests.addTests(suite) - - modules = _get_module_doctests() - for module in modules: - suite = doctest.DocTestSuite(module) - tests.addTests(suite) + suites = get_module_doctests() + tests.addTests(suites) return tests - - -def _get_module_doctests(): - modules = [] - - for pkg in pkgutil.walk_packages([SOURCE_DIR]): - # The importer is a pkgutil.ImpImporter instance: - # - # http://docs.python.org/library/pkgutil.html#pkgutil.ImpImporter - # - importer, module_name, is_package = pkg - if is_package: - # Otherwise, we will get the following error when adding tests: - # - # ValueError: (, 'has no tests') - # - continue - # The loader is a pkgutil.ImpLoader instance. - loader = importer.find_module(module_name) - try: - module = loader.load_module(module_name) - except ImportError, e: - # In some situations, the test harness was swallowing and/or - # suppressing the display of the stack trace when errors - # occurred here. The following code makes errors occurring here - # easier to troubleshoot. - details = "".join(traceback.format_exception(*sys.exc_info())) - raise ImportError(details) - modules.append(module) - - return modules -- cgit v1.2.1 From 60a6c396b55b986f823d34e67b25adc44791e684 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 20:32:29 -0700 Subject: Renamed SOURCE_DIR to PACKAGE_DIR. --- pystache/tests/common.py | 9 ++++++--- pystache/tests/doctesting.py | 15 ++++++--------- pystache/tests/test_doctests.py | 5 ----- 3 files changed, 12 insertions(+), 17 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 6aa0213..47ba54d 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -17,10 +17,13 @@ _TESTS_DIR = os.path.dirname(pystache.tests.__file__) DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'. EXAMPLES_DIR = os.path.dirname(examples.__file__) -# TODO: change SOURCE_DIR to PACKAGE_DIR. -SOURCE_DIR = os.path.dirname(pystache.__file__) -PROJECT_DIR = os.path.join(SOURCE_DIR, '..') +PACKAGE_DIR = os.path.dirname(pystache.__file__) +PROJECT_DIR = os.path.join(PACKAGE_DIR, '..') SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') +# TEXT_DOCTEST_PATHS: the paths to text files (i.e. non-module files) +# containing doctests. These paths should be OS-specific and relative +# to the project directory. +TEXT_DOCTEST_PATHS = ['README.rst'] def html_escape(u): diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index a0f116d..4a5f9f5 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -10,15 +10,12 @@ import pkgutil import doctest import traceback +from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, TEXT_DOCTEST_PATHS -from pystache.tests.common import PROJECT_DIR, SOURCE_DIR - - -# The paths to text files (i.e. non-module files) containing doctests. -# Paths should be OS-specific and relative to the project directory. -TEXT_DOCTEST_PATHS = ['README.rst'] - - +# This module follows the guidance documented here: +# +# http://docs.python.org/library/doctest.html#unittest-api +# def get_module_doctests(): """ @@ -39,7 +36,7 @@ def get_module_doctests(): suite = doctest.DocFileSuite(path, module_relative=False) suites.append(suite) - modules = _get_module_doctests(SOURCE_DIR) + modules = _get_module_doctests(PACKAGE_DIR) for module in modules: suite = doctest.DocTestSuite(module) suites.append(suite) diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py index 5ff8b8a..bdc3428 100644 --- a/pystache/tests/test_doctests.py +++ b/pystache/tests/test_doctests.py @@ -5,11 +5,6 @@ Creates unittest.TestSuite instances for the doctests in the project. """ -# This module follows the guidance documented here: -# -# http://docs.python.org/library/doctest.html#unittest-api -# - from pystache.tests.doctesting import get_module_doctests -- cgit v1.2.1 From 3e20e42308a4cfafd1190d843f7c5897245e7fe7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 20:56:54 -0700 Subject: Removed test_doctests.py (and use of the load_tests protocol). --- pystache/commands/test.py | 7 +++---- pystache/tests/main.py | 19 ++++++++++--------- pystache/tests/test_doctests.py | 35 ----------------------------------- 3 files changed, 13 insertions(+), 48 deletions(-) delete mode 100644 pystache/tests/test_doctests.py (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py index 4bf0a4b..61b2a78 100644 --- a/pystache/commands/test.py +++ b/pystache/commands/test.py @@ -7,13 +7,12 @@ This module provides a command to test pystache (unit tests, doctests, etc). import sys -import pystache -from pystache.tests.main import Tester +from pystache.tests.main import TestHarness def main(sys_argv=sys.argv): - tester = Tester() - tester.run_tests(package=pystache, sys_argv=sys_argv) + harness = TestHarness() + harness.run_tests(sys_argv=sys_argv) if __name__=='__main__': diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 0cd3e8e..e460a08 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -11,6 +11,9 @@ import os import sys from unittest import TestProgram +from pystache.tests.common import PACKAGE_DIR +from pystache.tests.doctesting import get_module_doctests + UNITTEST_FILE_PREFIX = "test_" @@ -93,12 +96,11 @@ def _get_test_module_names(package_dir): return modules -def _discover_test_modules(package): +def _discover_test_modules(package_dir): """ Discover and return a sorted list of the names of unit-test modules. """ - package_dir = os.path.dirname(package.__file__) modules = _get_test_module_names(package_dir) modules.sort() @@ -119,33 +121,32 @@ class _PystacheTestProgram(TestProgram): """ def runTests(self): - # TODO: add doctests, etc. to the self.test TestSuite. + doctest_suites = get_module_doctests() + self.test.addTests(doctest_suites) + TestProgram.runTests(self) -class Tester(object): +class TestHarness(object): """ Discovers and runs unit tests. """ - # TODO: consider replacing the package argument with a package_dir argument. - def run_tests(self, package, sys_argv): + def run_tests(self, sys_argv): """ Run all unit tests inside the given package. Arguments: - package: a module instance corresponding to the package. - sys_argv: a reference to sys.argv. """ if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): # Then no explicit module or test names were provided, so # auto-detect all unit tests. - module_names = _discover_test_modules(package) + module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) # We pass None for the module because we do not want the unittest diff --git a/pystache/tests/test_doctests.py b/pystache/tests/test_doctests.py deleted file mode 100644 index bdc3428..0000000 --- a/pystache/tests/test_doctests.py +++ /dev/null @@ -1,35 +0,0 @@ -# coding: utf-8 - -""" -Creates unittest.TestSuite instances for the doctests in the project. - -""" - -from pystache.tests.doctesting import get_module_doctests - - -# The following load_tests() function implements unittests's load_tests -# protocol added in Python 2.7: -# -# http://docs.python.org/library/unittest.html#load-tests-protocol -# -# Using this protocol lets us include the doctests in test runs without -# using nose, for example when using Distribute's test as in the following: -# -# python setup.py test -# -# Normally, nosetests would interpret this function as a test case (because -# its name matches the test regular expression) and call it with zero arguments -# as opposed to the required three. However, we are able to exclude it with -# an entry like the following in setup.cfg: -# -# exclude=load_tests -# -# TODO: find a substitute for the load_tests protocol for Python versions -# before version 2.7. -# -def load_tests(loader, tests, ignore): - suites = get_module_doctests() - tests.addTests(suites) - - return tests -- cgit v1.2.1 From 3e2ee32d9218f918eee2f00285f1d03c1269d97c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 21:13:53 -0700 Subject: Replaced the TestHarness class with a run_tests() function. --- pystache/commands/test.py | 5 ++--- pystache/tests/main.py | 57 +++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py index 61b2a78..aec1ff6 100644 --- a/pystache/commands/test.py +++ b/pystache/commands/test.py @@ -7,12 +7,11 @@ This module provides a command to test pystache (unit tests, doctests, etc). import sys -from pystache.tests.main import TestHarness +from pystache.tests.main import run_tests def main(sys_argv=sys.argv): - harness = TestHarness() - harness.run_tests(sys_argv=sys_argv) + run_tests(sys_argv=sys_argv) if __name__=='__main__': diff --git a/pystache/tests/main.py b/pystache/tests/main.py index e460a08..c3edae0 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -1,7 +1,7 @@ # coding: utf-8 """ -Allows all tests to be run. +Exposes a run_tests() function that runs all tests in the project. This module is for our test console script. @@ -34,6 +34,30 @@ UNITTEST_FILE_PREFIX = "test_" # TestCase or TestSuite instances (e.g. doctests and spec tests), and then # call the base class's runTests(). +def run_tests(sys_argv): + """ + Run all tests in the project. + + Arguments: + + sys_argv: a reference to sys.argv. + + """ + if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): + # Then no explicit module or test names were provided, so + # auto-detect all unit tests. + module_names = _discover_test_modules(PACKAGE_DIR) + sys_argv.extend(module_names) + + # We pass None for the module because we do not want the unittest + # module to resolve module names relative to a given module. + # (This would require importing all of the unittest modules from + # this module.) See the loadTestsFromName() method of the + # unittest.TestLoader class for more details on this parameter. + _PystacheTestProgram(argv=sys_argv, module=None) + # No need to return since unitttest.main() exits. + + def _find_unittest_files(package_dir): """ Return a list of paths to all unit-test files in the given package directory. @@ -125,34 +149,3 @@ class _PystacheTestProgram(TestProgram): self.test.addTests(doctest_suites) TestProgram.runTests(self) - - -class TestHarness(object): - - """ - Discovers and runs unit tests. - - """ - - def run_tests(self, sys_argv): - """ - Run all unit tests inside the given package. - - Arguments: - - sys_argv: a reference to sys.argv. - - """ - if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): - # Then no explicit module or test names were provided, so - # auto-detect all unit tests. - module_names = _discover_test_modules(PACKAGE_DIR) - sys_argv.extend(module_names) - - # We pass None for the module because we do not want the unittest - # module to resolve module names relative to a given module. - # (This would require importing all of the unittest modules from - # this module.) See the loadTestsFromName() method of the - # unittest.TestLoader class for more details on this parameter. - _PystacheTestProgram(argv=sys_argv, module=None) - # No need to return since unitttest.main() exits. -- cgit v1.2.1 From e1a91258995ef03be75719d2d992d516131168e8 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 21:22:10 -0700 Subject: Adjusted comments in pystache/tests/main.py. --- pystache/tests/main.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index c3edae0..d9048a0 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -17,23 +17,7 @@ from pystache.tests.doctesting import get_module_doctests UNITTEST_FILE_PREFIX = "test_" -# TODO: enhance this to work with doctests (instead of using the load_tests -# protocol), etc. - -# Notes for TODO: -# -# The function unittest.main() is an alias for unittest.TestProgram's -# constructor. The constructor calls self.runTests() as its final step, which -# expects self.test to be set. The constructor sets the self.test attribute -# by calling one of self.testLoader's "loadTests" methods. These methods -# return a unittest.TestSuite instance. Thus, self.test is set to a TestSuite -# instance prior to calling runTests(). -# -# Our strategy is to subclass unittest.TestProgram and override its runTests() -# method. Our implementation of runTests() will add to self.test additional -# TestCase or TestSuite instances (e.g. doctests and spec tests), and then -# call the base class's runTests(). - +# TODO: enhance this function to create spec-test tests. def run_tests(sys_argv): """ Run all tests in the project. @@ -136,7 +120,13 @@ def _discover_test_modules(package_dir): return modules - +# The function unittest.main() is an alias for unittest.TestProgram's +# constructor. TestProgram's constructor calls self.runTests() as its +# final step, which expects self.test to be set. The constructor sets +# the self.test attribute by calling one of self.testLoader's "loadTests" +# methods prior to callint self.runTests(). Each loadTest method returns +# a unittest.TestSuite instance. Thus, self.test is set to a TestSuite +# instance prior to calling runTests(). class _PystacheTestProgram(TestProgram): """ @@ -146,6 +136,8 @@ class _PystacheTestProgram(TestProgram): def runTests(self): doctest_suites = get_module_doctests() + # self.test is a unittest.TestSuite instance: + # http://docs.python.org/library/unittest.html#unittest.TestSuite self.test.addTests(doctest_suites) TestProgram.runTests(self) -- cgit v1.2.1 From 7b4a1084d5d4c86115feeda3c1b5d96261b0dd64 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 22:13:21 -0700 Subject: Added a hack to get around Python bug #7559: http://bugs.python.org/issue7559 --- pystache/commands/render.py | 11 ++++++++++- pystache/tests/test_mustachespec.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/render.py b/pystache/commands/render.py index cfe1652..23b19f8 100644 --- a/pystache/commands/render.py +++ b/pystache/commands/render.py @@ -13,7 +13,16 @@ try: except: # The json module is new in Python 2.6, whereas simplejson is # compatible with earlier versions. - import simplejson as json + try: + import simplejson as json + except ImportError: + # Raise an error with a type different from ImportError as a hack around + # this issue: + # http://bugs.python.org/issue7559 + from sys import exc_info + ex_type, ex_value, tb = exc_info() + new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) + raise new_ex.__class__, new_ex, tb # The optparse module is deprecated in Python 2.7 in favor of argparse. # However, argparse is not available in Python 2.6 and earlier. diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py index ba87d2c..ca084fe 100644 --- a/pystache/tests/test_mustachespec.py +++ b/pystache/tests/test_mustachespec.py @@ -24,7 +24,16 @@ except ImportError: # simplejson is. The simplejson package dropped support for Python 2.4 # in simplejson v2.1.0, so Python 2.4 requires a simplejson install # older than the most recent version. - import simplejson as json + try: + import simplejson as json + except ImportError: + # Raise an error with a type different from ImportError as a hack around + # this issue: + # http://bugs.python.org/issue7559 + from sys import exc_info + ex_type, ex_value, tb = exc_info() + new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) + raise new_ex.__class__, new_ex, tb file_extension = 'json' parser = json else: -- cgit v1.2.1 From 8f2d0def5a208afdbd198d2746f3df32037f2bac Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Fri, 20 Apr 2012 22:21:27 -0700 Subject: Added extra line --- pystache/tests/main.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index d9048a0..5db267a 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -104,6 +104,7 @@ def _get_test_module_names(package_dir): return modules + def _discover_test_modules(package_dir): """ Discover and return a sorted list of the names of unit-test modules. -- cgit v1.2.1 From 88c5c3df172c8e222582f6dfd66f4b341ac2dd81 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 08:24:11 -0700 Subject: Deleted a trailing space. --- pystache/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index b7468e0..84595b7 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -4,5 +4,5 @@ from pystache.init import * # TODO: make sure that "from pystache import *" exposes only the following: -# ['__version__', 'render', 'Renderer', 'TemplateSpec'] +# ['__version__', 'render', 'Renderer', 'TemplateSpec'] # and add a unit test for this. -- cgit v1.2.1 From ba5559b5e00cc83a663492934ba63c7210b471ea Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 08:26:01 -0700 Subject: Updated a comment in tests/common.py. --- pystache/tests/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 47ba54d..a9f42f8 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -21,8 +21,7 @@ PACKAGE_DIR = os.path.dirname(pystache.__file__) PROJECT_DIR = os.path.join(PACKAGE_DIR, '..') SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') # TEXT_DOCTEST_PATHS: the paths to text files (i.e. non-module files) -# containing doctests. These paths should be OS-specific and relative -# to the project directory. +# containing doctests. The paths should be relative to the project directory. TEXT_DOCTEST_PATHS = ['README.rst'] -- cgit v1.2.1 From fb12f8a6528357e6a1be7fd550040c2f9b213b2c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 08:29:12 -0700 Subject: The project directory can now be passed to pystache-test (for doctest purposes). --- pystache/tests/doctesting.py | 10 ++++------ pystache/tests/main.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index 4a5f9f5..04f1c87 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -10,14 +10,14 @@ import pkgutil import doctest import traceback -from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, TEXT_DOCTEST_PATHS +from pystache.tests.common import PACKAGE_DIR, TEXT_DOCTEST_PATHS # This module follows the guidance documented here: # # http://docs.python.org/library/doctest.html#unittest-api # -def get_module_doctests(): +def get_module_doctests(project_dir): """ Return a list of TestSuite instances for all doctests in the pacakqge. @@ -25,13 +25,11 @@ def get_module_doctests(): suites = [] # Since module_relative is False in our calls to DocFileSuite below, - # paths should be OS-specific. Moreover, we choose absolute paths - # so that the current working directory does not come into play. - # See the following for more info-- + # paths should be OS-specific. See the following for more info-- # # http://docs.python.org/library/doctest.html#doctest.DocFileSuite # - paths = [os.path.join(PROJECT_DIR, path) for path in TEXT_DOCTEST_PATHS] + paths = [os.path.normpath(os.path.join(project_dir, path)) for path in TEXT_DOCTEST_PATHS] for path in paths: suite = doctest.DocFileSuite(path, module_relative=False) suites.append(suite) diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 5db267a..799b7a6 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -11,7 +11,7 @@ import os import sys from unittest import TestProgram -from pystache.tests.common import PACKAGE_DIR +from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR from pystache.tests.doctesting import get_module_doctests @@ -27,12 +27,21 @@ def run_tests(sys_argv): sys_argv: a reference to sys.argv. """ + try: + # TODO: use optparse command options instead. + project_dir = sys_argv[1] + sys_argv.pop() + except IndexError: + project_dir = PROJECT_DIR + if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): # Then no explicit module or test names were provided, so # auto-detect all unit tests. module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) + _PystacheTestProgram._project_dir = project_dir + # We pass None for the module because we do not want the unittest # module to resolve module names relative to a given module. # (This would require importing all of the unittest modules from @@ -136,7 +145,7 @@ class _PystacheTestProgram(TestProgram): """ def runTests(self): - doctest_suites = get_module_doctests() + doctest_suites = get_module_doctests(self._project_dir) # self.test is a unittest.TestSuite instance: # http://docs.python.org/library/unittest.html#unittest.TestSuite self.test.addTests(doctest_suites) -- cgit v1.2.1 From 82442c3300ce47042bd8364fe05a6ab512c4e9a4 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 08:43:17 -0700 Subject: Renamed get_module_doctests() to get_doctests(). --- pystache/tests/doctesting.py | 11 ++++++++--- pystache/tests/main.py | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index 04f1c87..3cc06c1 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -17,9 +17,14 @@ from pystache.tests.common import PACKAGE_DIR, TEXT_DOCTEST_PATHS # http://docs.python.org/library/doctest.html#unittest-api # -def get_module_doctests(project_dir): +def get_doctests(text_file_dir): """ - Return a list of TestSuite instances for all doctests in the pacakqge. + Return a list of TestSuite instances for all doctests in the project. + + Arguments: + + text_file_dir: the directory in which to search for all text files + (i.e. non-module files) containing doctests. """ suites = [] @@ -29,7 +34,7 @@ def get_module_doctests(project_dir): # # http://docs.python.org/library/doctest.html#doctest.DocFileSuite # - paths = [os.path.normpath(os.path.join(project_dir, path)) for path in TEXT_DOCTEST_PATHS] + paths = [os.path.normpath(os.path.join(text_file_dir, path)) for path in TEXT_DOCTEST_PATHS] for path in paths: suite = doctest.DocFileSuite(path, module_relative=False) suites.append(suite) diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 799b7a6..d95c249 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -12,7 +12,7 @@ import sys from unittest import TestProgram from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR -from pystache.tests.doctesting import get_module_doctests +from pystache.tests.doctesting import get_doctests UNITTEST_FILE_PREFIX = "test_" @@ -145,7 +145,7 @@ class _PystacheTestProgram(TestProgram): """ def runTests(self): - doctest_suites = get_module_doctests(self._project_dir) + doctest_suites = get_doctests(self._project_dir) # self.test is a unittest.TestSuite instance: # http://docs.python.org/library/unittest.html#unittest.TestSuite self.test.addTests(doctest_suites) -- cgit v1.2.1 From ae755339578bebdf1134f1b8abf77dd8dceaabc9 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 10:35:26 -0700 Subject: Refactored pystache/tests/test_mustachespec.py to expose get_spec_tests(). --- pystache/tests/main.py | 16 ++- pystache/tests/test_mustachespec.py | 198 ++++++++++++++++++++++-------------- 2 files changed, 134 insertions(+), 80 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index d95c249..7cfcfeb 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -11,8 +11,10 @@ import os import sys from unittest import TestProgram -from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR +from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, SPEC_TEST_DIR from pystache.tests.doctesting import get_doctests +# TODO: change this to pystache.tests.spectesting. +from pystache.tests.test_mustachespec import get_spec_tests UNITTEST_FILE_PREFIX = "test_" @@ -40,7 +42,8 @@ def run_tests(sys_argv): module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) - _PystacheTestProgram._project_dir = project_dir + _PystacheTestProgram._text_doctest_dir = project_dir + _PystacheTestProgram._spec_test_dir = SPEC_TEST_DIR # We pass None for the module because we do not want the unittest # module to resolve module names relative to a given module. @@ -145,9 +148,14 @@ class _PystacheTestProgram(TestProgram): """ def runTests(self): - doctest_suites = get_doctests(self._project_dir) # self.test is a unittest.TestSuite instance: # http://docs.python.org/library/unittest.html#unittest.TestSuite - self.test.addTests(doctest_suites) + tests = self.test + + doctest_suites = get_doctests(self._text_doctest_dir) + tests.addTests(doctest_suites) + + spec_testcases = get_spec_tests(self._spec_test_dir) + tests.addTests(spec_testcases) TestProgram.runTests(self) diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py index ca084fe..273beed 100644 --- a/pystache/tests/test_mustachespec.py +++ b/pystache/tests/test_mustachespec.py @@ -1,11 +1,15 @@ # coding: utf-8 """ +Exposes a get_spec_tests() function for the project's test harness. + Creates a unittest.TestCase for the tests defined in the mustache spec. """ # TODO: this module can be cleaned up somewhat. +# TODO: move all of this code to pystache/tests/spectesting.py and +# have it expose a get_spec_tests(spec_test_dir) function. FILE_ENCODING = 'utf-8' # the encoding of the spec test files. @@ -49,12 +53,114 @@ import unittest import pystache from pystache import common from pystache.renderer import Renderer -from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR +from pystache.tests.common import AssertStringMixin + + +def get_spec_tests(spec_test_dir): + """ + Return a list of unittest.TestCase instances. + + """ + cases = [] + + # Make this absolute for easier diagnosis in case of error. + spec_test_dir = os.path.abspath(spec_test_dir) + spec_paths = glob.glob(os.path.join(spec_test_dir, '*.%s' % file_extension)) + + for path in spec_paths: + b = common.read(path) + u = unicode(b, encoding=FILE_ENCODING) + spec_data = parse(u) + tests = spec_data['tests'] + + for data in tests: + case = _deserialize_spec_test(data, path) + cases.append(case) + + # This test case lets us alert the user that spec tests are missing. + class CheckSpecTestsFound(unittest.TestCase): + + def runTest(self): + if len(cases) > 0: + return + raise Exception("Spec tests not found in: %s\n " + "Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir)) + + case = CheckSpecTestsFound() + cases.append(case) + + return cases + + +def _deserialize_spec_test(data, file_path): + """ + Return a unittest.TestCase instance representing a spec test. + + Arguments: + + data: the dictionary of attributes for a single test. + + """ + context = data['data'] + description = data['desc'] + # PyYAML seems to leave ASCII strings as byte strings. + expected = unicode(data['expected']) + # TODO: switch to using dict.get(). + partials = data.has_key('partials') and data['partials'] or {} + template = data['template'] + test_name = data['name'] + + # Convert code strings to functions. + # TODO: make this section of code easier to understand. + new_context = {} + for key, val in context.iteritems(): + if isinstance(val, dict) and val.get('__tag__') == 'code': + val = eval(val['python']) + new_context[key] = val + + test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path) + + return test_case + + +def _make_spec_test(expected, template, context, partials, description, test_name, file_path): + """ + Return a unittest.TestCase instance representing a spec test. + + """ + file_name = os.path.basename(file_path) + test_method_name = "Mustache spec (%s): %s" % (file_name, repr(test_name)) + + # We subclass SpecTestBase in order to control the test method name (for + # the purposes of improved reporting). + class SpecTest(SpecTestBase): + pass + + def run_test(self): + self._runTest() + + # TODO: should we restore this logic somewhere? + # If we don't convert unicode to str, we get the following error: + # "TypeError: __name__ must be set to a string object" + # test.__name__ = str(name) + setattr(SpecTest, test_method_name, run_test) + case = SpecTest(test_method_name) + + case._context = context + case._description = description + case._expected = expected + case._file_path = file_path + case._partials = partials + case._template = template + case._test_name = test_name + + return case def parse(u): """ - Parse + Parse the contents of a spec test file, and return a dict. + Arguments: u: a unicode string. @@ -87,51 +193,19 @@ def parse(u): return yaml.load(u) -# This test case lets us alert the user that spec tests are missing. -class CheckSpecTestsFound(unittest.TestCase): - - def test_spec_tests_found(self): - if len(spec_paths) > 0: - return - raise Exception("Spec tests not found in: %s\n " - "Consult the README file on how to add the Mustache spec tests." % repr(SPEC_TEST_DIR)) - - -# TODO: give this a name better than MustacheSpec. -class MustacheSpec(unittest.TestCase, AssertStringMixin): - pass - - -def buildTest(testData, spec_filename, parser): - """ - Arguments: - - parser: the module used for parsing (e.g. yaml or json). - - """ - - name = testData['name'] - description = testData['desc'] +class SpecTestBase(unittest.TestCase, AssertStringMixin): - test_name = "%s (%s)" % (name, spec_filename) - - def test(self): - template = testData['template'] - partials = testData.has_key('partials') and testData['partials'] or {} - # PyYAML seems to leave ASCII strings as byte strings. - expected = unicode(testData['expected']) - data = testData['data'] - - # Convert code strings to functions. - # TODO: make this section of code easier to understand. - new_data = {} - for key, val in data.iteritems(): - if isinstance(val, dict) and val.get('__tag__') == 'code': - val = eval(val['python']) - new_data[key] = val + def _runTest(self): + context = self._context + description = self._description + expected = self._expected + file_path = self._file_path + partials = self._partials + template = self._template + test_name = self._test_name renderer = Renderer(partials=partials) - actual = renderer.render(template, new_data) + actual = renderer.render(template, context) # We need to escape the strings that occur in our format string because # they can contain % symbols, for example (in delimiters.yml)-- @@ -141,11 +215,13 @@ def buildTest(testData, spec_filename, parser): def escape(s): return s.replace("%", "%%") - subs = [description, template, parser.__version__, str(parser)] + subs = [repr(test_name), description, os.path.abspath(file_path), template, parser.__version__, str(parser)] subs = tuple([escape(sub) for sub in subs]) # We include the parsing module version info to help with troubleshooting # yaml/json/simplejson issues. - message = """%s + message = """%s: %s + + File: %s Template: \"""%s\""" @@ -155,33 +231,3 @@ def buildTest(testData, spec_filename, parser): """ % subs self.assertString(actual, expected, format=message) - - # The name must begin with "test" for nosetests test discovery to work. - name = 'test: "%s"' % test_name - - # If we don't convert unicode to str, we get the following error: - # "TypeError: __name__ must be set to a string object" - test.__name__ = str(name) - - return test - - -spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) -for path in spec_paths: - - file_name = os.path.basename(path) - - b = common.read(path) - u = unicode(b, encoding=FILE_ENCODING) - spec_data = parse(u) - - tests = spec_data['tests'] - - for test in tests: - test = buildTest(test, file_name, parser) - setattr(MustacheSpec, test.__name__, test) - # Prevent this variable from being interpreted as another test. - del(test) - -if __name__ == '__main__': - unittest.main() -- cgit v1.2.1 From 150d4c45563015b60afe79f6b011c0b21f6594b7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 11:03:09 -0700 Subject: The pystache-tests script now accepts a spec test directory argument. --- pystache/tests/main.py | 11 +++++++++-- pystache/tests/test_mustachespec.py | 10 +++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 7cfcfeb..5b08471 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -32,10 +32,17 @@ def run_tests(sys_argv): try: # TODO: use optparse command options instead. project_dir = sys_argv[1] - sys_argv.pop() + sys_argv.pop(1) except IndexError: project_dir = PROJECT_DIR + try: + # TODO: use optparse command options instead. + spec_test_dir = sys_argv[1] + sys_argv.pop(1) + except IndexError: + spec_test_dir = SPEC_TEST_DIR + if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): # Then no explicit module or test names were provided, so # auto-detect all unit tests. @@ -43,7 +50,7 @@ def run_tests(sys_argv): sys_argv.extend(module_names) _PystacheTestProgram._text_doctest_dir = project_dir - _PystacheTestProgram._spec_test_dir = SPEC_TEST_DIR + _PystacheTestProgram._spec_test_dir = spec_test_dir # We pass None for the module because we do not want the unittest # module to resolve module names relative to a given module. diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py index 273beed..453c5ea 100644 --- a/pystache/tests/test_mustachespec.py +++ b/pystache/tests/test_mustachespec.py @@ -77,14 +77,18 @@ def get_spec_tests(spec_test_dir): case = _deserialize_spec_test(data, path) cases.append(case) + # Store this as a value so that CheckSpecTestsFound is not checking + # a reference to cases that contains itself. + spec_test_count = len(cases) + # This test case lets us alert the user that spec tests are missing. class CheckSpecTestsFound(unittest.TestCase): def runTest(self): - if len(cases) > 0: + if spec_test_count > 0: return - raise Exception("Spec tests not found in: %s\n " - "Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir)) + raise Exception("Spec tests not found--\n in %s\n" + " Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir)) case = CheckSpecTestsFound() cases.append(case) -- cgit v1.2.1 From 6e61879722cfebe58635a3a3cecc8c6b3e4b98b5 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 11:07:35 -0700 Subject: Moved test_mustachespec.py to spectesting.py since it no longer contains unit tests. --- pystache/tests/main.py | 3 +- pystache/tests/spectesting.py | 237 ++++++++++++++++++++++++++++++++++++ pystache/tests/test_mustachespec.py | 237 ------------------------------------ 3 files changed, 238 insertions(+), 239 deletions(-) create mode 100644 pystache/tests/spectesting.py delete mode 100644 pystache/tests/test_mustachespec.py (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 5b08471..82843a5 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -13,8 +13,7 @@ from unittest import TestProgram from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, SPEC_TEST_DIR from pystache.tests.doctesting import get_doctests -# TODO: change this to pystache.tests.spectesting. -from pystache.tests.test_mustachespec import get_spec_tests +from pystache.tests.spectesting import get_spec_tests UNITTEST_FILE_PREFIX = "test_" diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py new file mode 100644 index 0000000..453c5ea --- /dev/null +++ b/pystache/tests/spectesting.py @@ -0,0 +1,237 @@ +# coding: utf-8 + +""" +Exposes a get_spec_tests() function for the project's test harness. + +Creates a unittest.TestCase for the tests defined in the mustache spec. + +""" + +# TODO: this module can be cleaned up somewhat. +# TODO: move all of this code to pystache/tests/spectesting.py and +# have it expose a get_spec_tests(spec_test_dir) function. + +FILE_ENCODING = 'utf-8' # the encoding of the spec test files. + +yaml = None + +try: + # We try yaml first since it is more convenient when adding and modifying + # test cases by hand (since the YAML is human-readable and is the master + # from which the JSON format is generated). + import yaml +except ImportError: + try: + import json + except: + # The module json is not available prior to Python 2.6, whereas + # simplejson is. The simplejson package dropped support for Python 2.4 + # in simplejson v2.1.0, so Python 2.4 requires a simplejson install + # older than the most recent version. + try: + import simplejson as json + except ImportError: + # Raise an error with a type different from ImportError as a hack around + # this issue: + # http://bugs.python.org/issue7559 + from sys import exc_info + ex_type, ex_value, tb = exc_info() + new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) + raise new_ex.__class__, new_ex, tb + file_extension = 'json' + parser = json +else: + file_extension = 'yml' + parser = yaml + + +import codecs +import glob +import os.path +import unittest + +import pystache +from pystache import common +from pystache.renderer import Renderer +from pystache.tests.common import AssertStringMixin + + +def get_spec_tests(spec_test_dir): + """ + Return a list of unittest.TestCase instances. + + """ + cases = [] + + # Make this absolute for easier diagnosis in case of error. + spec_test_dir = os.path.abspath(spec_test_dir) + spec_paths = glob.glob(os.path.join(spec_test_dir, '*.%s' % file_extension)) + + for path in spec_paths: + b = common.read(path) + u = unicode(b, encoding=FILE_ENCODING) + spec_data = parse(u) + tests = spec_data['tests'] + + for data in tests: + case = _deserialize_spec_test(data, path) + cases.append(case) + + # Store this as a value so that CheckSpecTestsFound is not checking + # a reference to cases that contains itself. + spec_test_count = len(cases) + + # This test case lets us alert the user that spec tests are missing. + class CheckSpecTestsFound(unittest.TestCase): + + def runTest(self): + if spec_test_count > 0: + return + raise Exception("Spec tests not found--\n in %s\n" + " Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir)) + + case = CheckSpecTestsFound() + cases.append(case) + + return cases + + +def _deserialize_spec_test(data, file_path): + """ + Return a unittest.TestCase instance representing a spec test. + + Arguments: + + data: the dictionary of attributes for a single test. + + """ + context = data['data'] + description = data['desc'] + # PyYAML seems to leave ASCII strings as byte strings. + expected = unicode(data['expected']) + # TODO: switch to using dict.get(). + partials = data.has_key('partials') and data['partials'] or {} + template = data['template'] + test_name = data['name'] + + # Convert code strings to functions. + # TODO: make this section of code easier to understand. + new_context = {} + for key, val in context.iteritems(): + if isinstance(val, dict) and val.get('__tag__') == 'code': + val = eval(val['python']) + new_context[key] = val + + test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path) + + return test_case + + +def _make_spec_test(expected, template, context, partials, description, test_name, file_path): + """ + Return a unittest.TestCase instance representing a spec test. + + """ + file_name = os.path.basename(file_path) + test_method_name = "Mustache spec (%s): %s" % (file_name, repr(test_name)) + + # We subclass SpecTestBase in order to control the test method name (for + # the purposes of improved reporting). + class SpecTest(SpecTestBase): + pass + + def run_test(self): + self._runTest() + + # TODO: should we restore this logic somewhere? + # If we don't convert unicode to str, we get the following error: + # "TypeError: __name__ must be set to a string object" + # test.__name__ = str(name) + setattr(SpecTest, test_method_name, run_test) + case = SpecTest(test_method_name) + + case._context = context + case._description = description + case._expected = expected + case._file_path = file_path + case._partials = partials + case._template = template + case._test_name = test_name + + return case + + +def parse(u): + """ + Parse the contents of a spec test file, and return a dict. + + Arguments: + + u: a unicode string. + + """ + # TODO: find a cleaner mechanism for choosing between the two. + if yaml is None: + # Then use json. + + # The only way to get the simplejson module to return unicode strings + # is to pass it unicode. See, for example-- + # + # http://code.google.com/p/simplejson/issues/detail?id=40 + # + # and the documentation of simplejson.loads(): + # + # "If s is a str then decoded JSON strings that contain only ASCII + # characters may be parsed as str for performance and memory reasons. + # If your code expects only unicode the appropriate solution is + # decode s to unicode prior to calling loads." + # + return json.loads(u) + # Otherwise, yaml. + + def code_constructor(loader, node): + value = loader.construct_mapping(node) + return eval(value['python'], {}) + + yaml.add_constructor(u'!code', code_constructor) + return yaml.load(u) + + +class SpecTestBase(unittest.TestCase, AssertStringMixin): + + def _runTest(self): + context = self._context + description = self._description + expected = self._expected + file_path = self._file_path + partials = self._partials + template = self._template + test_name = self._test_name + + renderer = Renderer(partials=partials) + actual = renderer.render(template, context) + + # We need to escape the strings that occur in our format string because + # they can contain % symbols, for example (in delimiters.yml)-- + # + # "template: '{{=<% %>=}}(<%text%>)'" + # + def escape(s): + return s.replace("%", "%%") + + subs = [repr(test_name), description, os.path.abspath(file_path), template, parser.__version__, str(parser)] + subs = tuple([escape(sub) for sub in subs]) + # We include the parsing module version info to help with troubleshooting + # yaml/json/simplejson issues. + message = """%s: %s + + File: %s + + Template: \"""%s\""" + + %%s + + (using version %s of %s) + """ % subs + + self.assertString(actual, expected, format=message) diff --git a/pystache/tests/test_mustachespec.py b/pystache/tests/test_mustachespec.py deleted file mode 100644 index 453c5ea..0000000 --- a/pystache/tests/test_mustachespec.py +++ /dev/null @@ -1,237 +0,0 @@ -# coding: utf-8 - -""" -Exposes a get_spec_tests() function for the project's test harness. - -Creates a unittest.TestCase for the tests defined in the mustache spec. - -""" - -# TODO: this module can be cleaned up somewhat. -# TODO: move all of this code to pystache/tests/spectesting.py and -# have it expose a get_spec_tests(spec_test_dir) function. - -FILE_ENCODING = 'utf-8' # the encoding of the spec test files. - -yaml = None - -try: - # We try yaml first since it is more convenient when adding and modifying - # test cases by hand (since the YAML is human-readable and is the master - # from which the JSON format is generated). - import yaml -except ImportError: - try: - import json - except: - # The module json is not available prior to Python 2.6, whereas - # simplejson is. The simplejson package dropped support for Python 2.4 - # in simplejson v2.1.0, so Python 2.4 requires a simplejson install - # older than the most recent version. - try: - import simplejson as json - except ImportError: - # Raise an error with a type different from ImportError as a hack around - # this issue: - # http://bugs.python.org/issue7559 - from sys import exc_info - ex_type, ex_value, tb = exc_info() - new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) - raise new_ex.__class__, new_ex, tb - file_extension = 'json' - parser = json -else: - file_extension = 'yml' - parser = yaml - - -import codecs -import glob -import os.path -import unittest - -import pystache -from pystache import common -from pystache.renderer import Renderer -from pystache.tests.common import AssertStringMixin - - -def get_spec_tests(spec_test_dir): - """ - Return a list of unittest.TestCase instances. - - """ - cases = [] - - # Make this absolute for easier diagnosis in case of error. - spec_test_dir = os.path.abspath(spec_test_dir) - spec_paths = glob.glob(os.path.join(spec_test_dir, '*.%s' % file_extension)) - - for path in spec_paths: - b = common.read(path) - u = unicode(b, encoding=FILE_ENCODING) - spec_data = parse(u) - tests = spec_data['tests'] - - for data in tests: - case = _deserialize_spec_test(data, path) - cases.append(case) - - # Store this as a value so that CheckSpecTestsFound is not checking - # a reference to cases that contains itself. - spec_test_count = len(cases) - - # This test case lets us alert the user that spec tests are missing. - class CheckSpecTestsFound(unittest.TestCase): - - def runTest(self): - if spec_test_count > 0: - return - raise Exception("Spec tests not found--\n in %s\n" - " Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir)) - - case = CheckSpecTestsFound() - cases.append(case) - - return cases - - -def _deserialize_spec_test(data, file_path): - """ - Return a unittest.TestCase instance representing a spec test. - - Arguments: - - data: the dictionary of attributes for a single test. - - """ - context = data['data'] - description = data['desc'] - # PyYAML seems to leave ASCII strings as byte strings. - expected = unicode(data['expected']) - # TODO: switch to using dict.get(). - partials = data.has_key('partials') and data['partials'] or {} - template = data['template'] - test_name = data['name'] - - # Convert code strings to functions. - # TODO: make this section of code easier to understand. - new_context = {} - for key, val in context.iteritems(): - if isinstance(val, dict) and val.get('__tag__') == 'code': - val = eval(val['python']) - new_context[key] = val - - test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path) - - return test_case - - -def _make_spec_test(expected, template, context, partials, description, test_name, file_path): - """ - Return a unittest.TestCase instance representing a spec test. - - """ - file_name = os.path.basename(file_path) - test_method_name = "Mustache spec (%s): %s" % (file_name, repr(test_name)) - - # We subclass SpecTestBase in order to control the test method name (for - # the purposes of improved reporting). - class SpecTest(SpecTestBase): - pass - - def run_test(self): - self._runTest() - - # TODO: should we restore this logic somewhere? - # If we don't convert unicode to str, we get the following error: - # "TypeError: __name__ must be set to a string object" - # test.__name__ = str(name) - setattr(SpecTest, test_method_name, run_test) - case = SpecTest(test_method_name) - - case._context = context - case._description = description - case._expected = expected - case._file_path = file_path - case._partials = partials - case._template = template - case._test_name = test_name - - return case - - -def parse(u): - """ - Parse the contents of a spec test file, and return a dict. - - Arguments: - - u: a unicode string. - - """ - # TODO: find a cleaner mechanism for choosing between the two. - if yaml is None: - # Then use json. - - # The only way to get the simplejson module to return unicode strings - # is to pass it unicode. See, for example-- - # - # http://code.google.com/p/simplejson/issues/detail?id=40 - # - # and the documentation of simplejson.loads(): - # - # "If s is a str then decoded JSON strings that contain only ASCII - # characters may be parsed as str for performance and memory reasons. - # If your code expects only unicode the appropriate solution is - # decode s to unicode prior to calling loads." - # - return json.loads(u) - # Otherwise, yaml. - - def code_constructor(loader, node): - value = loader.construct_mapping(node) - return eval(value['python'], {}) - - yaml.add_constructor(u'!code', code_constructor) - return yaml.load(u) - - -class SpecTestBase(unittest.TestCase, AssertStringMixin): - - def _runTest(self): - context = self._context - description = self._description - expected = self._expected - file_path = self._file_path - partials = self._partials - template = self._template - test_name = self._test_name - - renderer = Renderer(partials=partials) - actual = renderer.render(template, context) - - # We need to escape the strings that occur in our format string because - # they can contain % symbols, for example (in delimiters.yml)-- - # - # "template: '{{=<% %>=}}(<%text%>)'" - # - def escape(s): - return s.replace("%", "%%") - - subs = [repr(test_name), description, os.path.abspath(file_path), template, parser.__version__, str(parser)] - subs = tuple([escape(sub) for sub in subs]) - # We include the parsing module version info to help with troubleshooting - # yaml/json/simplejson issues. - message = """%s: %s - - File: %s - - Template: \"""%s\""" - - %%s - - (using version %s of %s) - """ % subs - - self.assertString(actual, expected, format=message) -- cgit v1.2.1 From 27a600e011de27743e78e4ec95aa53af759a4be0 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 11:36:34 -0700 Subject: Fixed a bug that affected the processing of ~lambdas.json (but not YAML). --- pystache/tests/spectesting.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py index 453c5ea..cb9d316 100644 --- a/pystache/tests/spectesting.py +++ b/pystache/tests/spectesting.py @@ -68,14 +68,8 @@ def get_spec_tests(spec_test_dir): spec_paths = glob.glob(os.path.join(spec_test_dir, '*.%s' % file_extension)) for path in spec_paths: - b = common.read(path) - u = unicode(b, encoding=FILE_ENCODING) - spec_data = parse(u) - tests = spec_data['tests'] - - for data in tests: - case = _deserialize_spec_test(data, path) - cases.append(case) + new_cases = _read_spec_tests(path) + cases.extend(new_cases) # Store this as a value so that CheckSpecTestsFound is not checking # a reference to cases that contains itself. @@ -96,6 +90,24 @@ def get_spec_tests(spec_test_dir): return cases +def _read_spec_tests(path): + """ + Return a list of unittest.TestCase instances. + + """ + b = common.read(path) + u = unicode(b, encoding=FILE_ENCODING) + spec_data = parse(u) + tests = spec_data['tests'] + + cases = [] + for data in tests: + case = _deserialize_spec_test(data, path) + cases.append(case) + + return cases + + def _deserialize_spec_test(data, file_path): """ Return a unittest.TestCase instance representing a spec test. @@ -105,7 +117,7 @@ def _deserialize_spec_test(data, file_path): data: the dictionary of attributes for a single test. """ - context = data['data'] + unconverted_context = data['data'] description = data['desc'] # PyYAML seems to leave ASCII strings as byte strings. expected = unicode(data['expected']) @@ -116,11 +128,11 @@ def _deserialize_spec_test(data, file_path): # Convert code strings to functions. # TODO: make this section of code easier to understand. - new_context = {} - for key, val in context.iteritems(): + context = {} + for key, val in unconverted_context.iteritems(): if isinstance(val, dict) and val.get('__tag__') == 'code': val = eval(val['python']) - new_context[key] = val + context[key] = val test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path) @@ -219,7 +231,8 @@ class SpecTestBase(unittest.TestCase, AssertStringMixin): def escape(s): return s.replace("%", "%%") - subs = [repr(test_name), description, os.path.abspath(file_path), template, parser.__version__, str(parser)] + subs = [repr(test_name), description, os.path.abspath(file_path), + template, repr(context), parser.__version__, str(parser)] subs = tuple([escape(sub) for sub in subs]) # We include the parsing module version info to help with troubleshooting # yaml/json/simplejson issues. @@ -229,6 +242,8 @@ class SpecTestBase(unittest.TestCase, AssertStringMixin): Template: \"""%s\""" + Context: %s + %%s (using version %s of %s) -- cgit v1.2.1 From e226768a7b69e580701e528f683ae4aabbfb85f7 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 13:51:28 -0700 Subject: The test script now converts text doctest files to Python 3 when needed. --- pystache/tests/doctesting.py | 45 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index 3cc06c1..1d82fde 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -5,13 +5,17 @@ Exposes a get_doctests() function for the project's test harness. """ +from lib2to3.main import main as lib2to3main +import doctest import os import pkgutil -import doctest +from shutil import copyfile +import sys import traceback from pystache.tests.common import PACKAGE_DIR, TEXT_DOCTEST_PATHS + # This module follows the guidance documented here: # # http://docs.python.org/library/doctest.html#unittest-api @@ -27,14 +31,19 @@ def get_doctests(text_file_dir): (i.e. non-module files) containing doctests. """ - suites = [] - # Since module_relative is False in our calls to DocFileSuite below, # paths should be OS-specific. See the following for more info-- # # http://docs.python.org/library/doctest.html#doctest.DocFileSuite # paths = [os.path.normpath(os.path.join(text_file_dir, path)) for path in TEXT_DOCTEST_PATHS] + + py_version = sys.version_info + if py_version >= (3,): + paths = _convert_paths(paths) + + suites = [] + for path in paths: suite = doctest.DocFileSuite(path, module_relative=False) suites.append(suite) @@ -47,6 +56,36 @@ def get_doctests(text_file_dir): return suites +def _convert_2to3(path): + """ + Convert the given file, and return the path to the converted files. + + """ + base, ext = os.path.splitext(path) + # For example, "README.temp2to3.rst". + new_path = "%s.temp2to3%s" % (base, ext) + + copyfile(path, new_path) + + args = ['--doctests_only', '--no-diffs', '--write', '--nobackups', new_path] + lib2to3main("lib2to3.fixes", args=args) + + return new_path + + +def _convert_paths(paths): + """ + Convert the given files, and return the paths to the converted files. + + """ + new_paths = [] + for path in paths: + new_path = _convert_2to3(path) + new_paths.append(new_path) + + return new_paths + + def _get_module_doctests(package_dir): modules = [] -- cgit v1.2.1 From 7a27321d831f645dc8f099d1edec0dd8de2a0d20 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 21 Apr 2012 13:57:18 -0700 Subject: Only pull in 2to3-related modules when running Python 3.x. --- pystache/tests/doctesting.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index 1d82fde..2c1b287 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -5,14 +5,18 @@ Exposes a get_doctests() function for the project's test harness. """ -from lib2to3.main import main as lib2to3main import doctest import os import pkgutil -from shutil import copyfile import sys import traceback +if sys.version_info >= (3,): + # Then pull in modules needed for 2to3 conversion. The modules + # below are not necessarily available in older versions of Python. + from lib2to3.main import main as lib2to3main # new in Python 2.6? + from shutil import copyfile + from pystache.tests.common import PACKAGE_DIR, TEXT_DOCTEST_PATHS @@ -38,8 +42,7 @@ def get_doctests(text_file_dir): # paths = [os.path.normpath(os.path.join(text_file_dir, path)) for path in TEXT_DOCTEST_PATHS] - py_version = sys.version_info - if py_version >= (3,): + if sys.version_info >= (3,): paths = _convert_paths(paths) suites = [] -- cgit v1.2.1 From 15e6db66367827efa9f7b00f7e2c97b20f1d81dd Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 06:50:52 -0700 Subject: Moved some of the test-discovery logic into tests/common.py --- pystache/tests/common.py | 66 ++++++++++++++++++++++++++++++++++++++ pystache/tests/main.py | 82 +++++++----------------------------------------- 2 files changed, 77 insertions(+), 71 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/common.py b/pystache/tests/common.py index a9f42f8..0cbefe4 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -24,6 +24,8 @@ SPEC_TEST_DIR = os.path.join(PROJECT_DIR, 'ext', 'spec', 'specs') # containing doctests. The paths should be relative to the project directory. TEXT_DOCTEST_PATHS = ['README.rst'] +UNITTEST_FILE_PREFIX = "test_" + def html_escape(u): """ @@ -45,6 +47,70 @@ def get_data_path(file_name): return os.path.join(DATA_DIR, file_name) +# Functions related to get_module_names(). + +def _find_files(root_dir, should_include): + """ + Return a list of paths to all files in the given directory. + + Arguments: + + should_include: a function that accepts a file path and returns True or False. + + """ + paths = [] # Return value. + + # os.walk() is new in Python 2.3 + # http://docs.python.org/library/os.html#os.walk + for dir_path, dir_names, file_names in os.walk(root_dir): + new_paths = [os.path.join(dir_path, file_name) for file_name in file_names] + new_paths = filter(should_include, new_paths) + paths.extend(new_paths) + + return paths + + +def _make_module_names(package_dir, paths): + """ + Return a list of fully-qualified module names given a list of module paths. + + """ + package_dir = os.path.abspath(package_dir) + package_name = os.path.split(package_dir)[1] + + prefix_length = len(package_dir) + + module_names = [] + for path in paths: + path = os.path.abspath(path) # for example /subpackage/module.py + rel_path = path[prefix_length:] # for example /subpackage/module.py + rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module + + parts = [] + while True: + (rel_path, tail) = os.path.split(rel_path) + if not tail: + break + parts.insert(0, tail) + # We now have, for example, ['subpackage', 'module']. + parts.insert(0, package_name) + module = ".".join(parts) + module_names.append(module) + + return module_names + + +def get_module_names(package_dir, should_include): + """ + Return a list of fully-qualified module names in the given package. + + """ + paths = _find_files(package_dir, should_include) + names = _make_module_names(package_dir, paths) + + return names + + class AssertStringMixin: """A unittest.TestCase mixin to check string equality.""" diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 82843a5..39ee227 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -11,13 +11,12 @@ import os import sys from unittest import TestProgram -from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, SPEC_TEST_DIR +from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, SPEC_TEST_DIR, UNITTEST_FILE_PREFIX +from pystache.tests.common import get_module_names from pystache.tests.doctesting import get_doctests from pystache.tests.spectesting import get_spec_tests -UNITTEST_FILE_PREFIX = "test_" - # TODO: enhance this function to create spec-test tests. def run_tests(sys_argv): """ @@ -60,83 +59,24 @@ def run_tests(sys_argv): # No need to return since unitttest.main() exits. -def _find_unittest_files(package_dir): - """ - Return a list of paths to all unit-test files in the given package directory. - - """ - paths = [] # Return value. - - def is_unittest(file_name): - return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') - - # os.walk() is new in Python 2.3 - # http://docs.python.org/library/os.html#os.walk - for dir_path, dir_names, file_names in os.walk(package_dir): - file_names = filter(is_unittest, file_names) - - for file_name in file_names: - path = os.path.join(dir_path, file_name) - paths.append(path) - - return paths - - -def _get_module_names(package_dir, paths): - """ - Return a list of fully-qualified test module names given a list of module paths. - - """ - package_dir = os.path.abspath(package_dir) - package_name = os.path.split(package_dir)[1] - - prefix_length = len(package_dir) - - module_names = [] - for path in paths: - path = os.path.abspath(path) # for example /subpackage/module.py - rel_path = path[prefix_length:] # for example /subpackage/module.py - rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module - - parts = [] - while True: - (rel_path, tail) = os.path.split(rel_path) - if not tail: - break - parts.insert(0, tail) - # We now have, for example, ['subpackage', 'module']. - parts.insert(0, package_name) - module = ".".join(parts) - module_names.append(module) - - return module_names - - -def _get_test_module_names(package_dir): - """ - Return a list of fully-qualified module names given a list of module paths. - - """ - paths = _find_unittest_files(package_dir) - modules = _get_module_names(package_dir, paths) - - return modules - - def _discover_test_modules(package_dir): """ Discover and return a sorted list of the names of unit-test modules. """ - modules = _get_test_module_names(package_dir) - modules.sort() + def is_unittest_module(path): + file_name = os.path.basename(path) + return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') + + names = get_module_names(package_dir, is_unittest_module) + names.sort() # This is a sanity check to ensure that the unit-test discovery # methods are working. - if len(modules) < 1: - raise Exception("No unit-test modules found.") + if len(names) < 1: + raise Exception("No unit-test modules found--\n in %s" % package_dir) - return modules + return names # The function unittest.main() is an alias for unittest.TestProgram's -- cgit v1.2.1 From 71a97ed2a94d45f60af474b94a624959f6c9bbd2 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 08:06:10 -0700 Subject: Added docstrings to all modules to avoid doctest bug 14649: http://bugs.python.org/issue14649 --- pystache/__init__.py | 6 ++++++ pystache/tests/data/views.py | 5 +++++ pystache/tests/examples/comments.py | 6 ++++++ pystache/tests/examples/complex.py | 6 ++++++ pystache/tests/examples/delimiters.py | 6 ++++++ pystache/tests/examples/double_section.py | 6 ++++++ pystache/tests/examples/escaped.py | 6 ++++++ pystache/tests/examples/inverted.py | 6 ++++++ pystache/tests/examples/lambdas.py | 6 ++++++ pystache/tests/examples/nested_context.py | 6 ++++++ pystache/tests/examples/partials_with_lambdas.py | 6 ++++++ pystache/tests/examples/readme.py | 6 ++++++ pystache/tests/examples/simple.py | 6 ++++++ pystache/tests/examples/template_partial.py | 6 ++++++ pystache/tests/examples/unescaped.py | 6 ++++++ pystache/tests/examples/unicode_input.py | 6 ++++++ pystache/tests/examples/unicode_output.py | 5 +++++ pystache/tests/test_examples.py | 5 +++++ 18 files changed, 105 insertions(+) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index 84595b7..eb3ee9f 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + # We keep all initialization code in a separate module. # TODO: consider doing something like this instead: # from pystache.init import __version__, render, Renderer, TemplateSpec diff --git a/pystache/tests/data/views.py b/pystache/tests/data/views.py index 4d9df02..0b96309 100644 --- a/pystache/tests/data/views.py +++ b/pystache/tests/data/views.py @@ -1,5 +1,10 @@ # coding: utf-8 +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class SayHello(object): diff --git a/pystache/tests/examples/comments.py b/pystache/tests/examples/comments.py index f9c3125..8d75f88 100644 --- a/pystache/tests/examples/comments.py +++ b/pystache/tests/examples/comments.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class Comments(object): def title(self): diff --git a/pystache/tests/examples/complex.py b/pystache/tests/examples/complex.py index e3f1767..c653db0 100644 --- a/pystache/tests/examples/complex.py +++ b/pystache/tests/examples/complex.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class Complex(object): def header(self): diff --git a/pystache/tests/examples/delimiters.py b/pystache/tests/examples/delimiters.py index a132ed0..a31ec1b 100644 --- a/pystache/tests/examples/delimiters.py +++ b/pystache/tests/examples/delimiters.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class Delimiters(object): def first(self): diff --git a/pystache/tests/examples/double_section.py b/pystache/tests/examples/double_section.py index 0bec602..c9736e4 100644 --- a/pystache/tests/examples/double_section.py +++ b/pystache/tests/examples/double_section.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class DoubleSection(object): def t(self): diff --git a/pystache/tests/examples/escaped.py b/pystache/tests/examples/escaped.py index fed1705..5d72dde 100644 --- a/pystache/tests/examples/escaped.py +++ b/pystache/tests/examples/escaped.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class Escaped(object): def title(self): diff --git a/pystache/tests/examples/inverted.py b/pystache/tests/examples/inverted.py index 2a05302..12212b4 100644 --- a/pystache/tests/examples/inverted.py +++ b/pystache/tests/examples/inverted.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class Inverted(object): diff --git a/pystache/tests/examples/lambdas.py b/pystache/tests/examples/lambdas.py index 653531d..3bc08ff 100644 --- a/pystache/tests/examples/lambdas.py +++ b/pystache/tests/examples/lambdas.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec def rot(s, n=13): diff --git a/pystache/tests/examples/nested_context.py b/pystache/tests/examples/nested_context.py index 4626ac0..a2661b9 100644 --- a/pystache/tests/examples/nested_context.py +++ b/pystache/tests/examples/nested_context.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class NestedContext(TemplateSpec): diff --git a/pystache/tests/examples/partials_with_lambdas.py b/pystache/tests/examples/partials_with_lambdas.py index 42d1b24..638aa36 100644 --- a/pystache/tests/examples/partials_with_lambdas.py +++ b/pystache/tests/examples/partials_with_lambdas.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache.tests.examples.lambdas import rot class PartialsWithLambdas(object): diff --git a/pystache/tests/examples/readme.py b/pystache/tests/examples/readme.py index 23b44f5..8dcee43 100644 --- a/pystache/tests/examples/readme.py +++ b/pystache/tests/examples/readme.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class SayHello(object): def to(self): return "Pizza" diff --git a/pystache/tests/examples/simple.py b/pystache/tests/examples/simple.py index 3252a81..4e611d0 100644 --- a/pystache/tests/examples/simple.py +++ b/pystache/tests/examples/simple.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class Simple(TemplateSpec): diff --git a/pystache/tests/examples/template_partial.py b/pystache/tests/examples/template_partial.py index a9052d7..1c4d1a0 100644 --- a/pystache/tests/examples/template_partial.py +++ b/pystache/tests/examples/template_partial.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class TemplatePartial(TemplateSpec): diff --git a/pystache/tests/examples/unescaped.py b/pystache/tests/examples/unescaped.py index 67c12ca..92889af 100644 --- a/pystache/tests/examples/unescaped.py +++ b/pystache/tests/examples/unescaped.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + class Unescaped(object): def title(self): diff --git a/pystache/tests/examples/unicode_input.py b/pystache/tests/examples/unicode_input.py index 2c10fcb..d045757 100644 --- a/pystache/tests/examples/unicode_input.py +++ b/pystache/tests/examples/unicode_input.py @@ -1,3 +1,9 @@ + +""" +TODO: add a docstring. + +""" + from pystache import TemplateSpec class UnicodeInput(TemplateSpec): diff --git a/pystache/tests/examples/unicode_output.py b/pystache/tests/examples/unicode_output.py index d5579c3..da0e1d2 100644 --- a/pystache/tests/examples/unicode_output.py +++ b/pystache/tests/examples/unicode_output.py @@ -1,5 +1,10 @@ # encoding: utf-8 +""" +TODO: add a docstring. + +""" + class UnicodeOutput(object): def name(self): diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py index d99478f..5c9f74d 100644 --- a/pystache/tests/test_examples.py +++ b/pystache/tests/test_examples.py @@ -1,5 +1,10 @@ # encoding: utf-8 +""" +TODO: add a docstring. + +""" + import unittest from examples.comments import Comments -- cgit v1.2.1 From 1806f853ab4fa8847aa4c1fbd7527d4ffcb4a603 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 08:24:45 -0700 Subject: Test script now works with Python 2.4; tox now works with Python 2.4 to 3.2. Doctest discovery no longer depends on pkgutil.walk_packages(). --- pystache/commands/__init__.py | 4 ++++ pystache/tests/__init__.py | 4 ++++ pystache/tests/common.py | 14 +++++++++++-- pystache/tests/data/__init__.py | 4 ++++ pystache/tests/data/locator/__init__.py | 4 ++++ pystache/tests/doctesting.py | 36 +++------------------------------ pystache/tests/examples/__init__.py | 4 ++++ pystache/tests/main.py | 5 ++--- 8 files changed, 37 insertions(+), 38 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/__init__.py b/pystache/commands/__init__.py index e69de29..a0d386a 100644 --- a/pystache/commands/__init__.py +++ b/pystache/commands/__init__.py @@ -0,0 +1,4 @@ +""" +TODO: add a docstring. + +""" diff --git a/pystache/tests/__init__.py b/pystache/tests/__init__.py index e69de29..a0d386a 100644 --- a/pystache/tests/__init__.py +++ b/pystache/tests/__init__.py @@ -0,0 +1,4 @@ +""" +TODO: add a docstring. + +""" diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 0cbefe4..a99e709 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -51,7 +51,7 @@ def get_data_path(file_name): def _find_files(root_dir, should_include): """ - Return a list of paths to all files in the given directory. + Return a list of paths to all modules below the given directory. Arguments: @@ -60,10 +60,13 @@ def _find_files(root_dir, should_include): """ paths = [] # Return value. + is_module = lambda path: path.endswith(".py") + # os.walk() is new in Python 2.3 # http://docs.python.org/library/os.html#os.walk for dir_path, dir_names, file_names in os.walk(root_dir): new_paths = [os.path.join(dir_path, file_name) for file_name in file_names] + new_paths = filter(is_module, new_paths) new_paths = filter(should_include, new_paths) paths.extend(new_paths) @@ -100,13 +103,20 @@ def _make_module_names(package_dir, paths): return module_names -def get_module_names(package_dir, should_include): +def get_module_names(package_dir=None, should_include=None): """ Return a list of fully-qualified module names in the given package. """ + if package_dir is None: + package_dir = PACKAGE_DIR + + if should_include is None: + should_include = lambda path: True + paths = _find_files(package_dir, should_include) names = _make_module_names(package_dir, paths) + names.sort() return names diff --git a/pystache/tests/data/__init__.py b/pystache/tests/data/__init__.py index e69de29..a0d386a 100644 --- a/pystache/tests/data/__init__.py +++ b/pystache/tests/data/__init__.py @@ -0,0 +1,4 @@ +""" +TODO: add a docstring. + +""" diff --git a/pystache/tests/data/locator/__init__.py b/pystache/tests/data/locator/__init__.py index e69de29..a0d386a 100644 --- a/pystache/tests/data/locator/__init__.py +++ b/pystache/tests/data/locator/__init__.py @@ -0,0 +1,4 @@ +""" +TODO: add a docstring. + +""" diff --git a/pystache/tests/doctesting.py b/pystache/tests/doctesting.py index 2c1b287..469c81e 100644 --- a/pystache/tests/doctesting.py +++ b/pystache/tests/doctesting.py @@ -17,7 +17,8 @@ if sys.version_info >= (3,): from lib2to3.main import main as lib2to3main # new in Python 2.6? from shutil import copyfile -from pystache.tests.common import PACKAGE_DIR, TEXT_DOCTEST_PATHS +from pystache.tests.common import TEXT_DOCTEST_PATHS +from pystache.tests.common import get_module_names # This module follows the guidance documented here: @@ -51,7 +52,7 @@ def get_doctests(text_file_dir): suite = doctest.DocFileSuite(path, module_relative=False) suites.append(suite) - modules = _get_module_doctests(PACKAGE_DIR) + modules = get_module_names() for module in modules: suite = doctest.DocTestSuite(module) suites.append(suite) @@ -87,34 +88,3 @@ def _convert_paths(paths): new_paths.append(new_path) return new_paths - - -def _get_module_doctests(package_dir): - modules = [] - - for pkg in pkgutil.walk_packages([package_dir]): - # The importer is a pkgutil.ImpImporter instance: - # - # http://docs.python.org/library/pkgutil.html#pkgutil.ImpImporter - # - importer, module_name, is_package = pkg - if is_package: - # Otherwise, we will get the following error when adding tests: - # - # ValueError: (, 'has no tests') - # - continue - # The loader is a pkgutil.ImpLoader instance. - loader = importer.find_module(module_name) - try: - module = loader.load_module(module_name) - except ImportError, e: - # In some situations, the test harness was swallowing and/or - # suppressing the display of the stack trace when errors - # occurred here. The following code makes errors occurring here - # easier to troubleshoot. - details = "".join(traceback.format_exception(*sys.exc_info())) - raise ImportError(details) - modules.append(module) - - return modules diff --git a/pystache/tests/examples/__init__.py b/pystache/tests/examples/__init__.py index e69de29..a0d386a 100644 --- a/pystache/tests/examples/__init__.py +++ b/pystache/tests/examples/__init__.py @@ -0,0 +1,4 @@ +""" +TODO: add a docstring. + +""" diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 39ee227..9364f39 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -66,10 +66,9 @@ def _discover_test_modules(package_dir): """ def is_unittest_module(path): file_name = os.path.basename(path) - return file_name.startswith(UNITTEST_FILE_PREFIX) and file_name.endswith('.py') + return file_name.startswith(UNITTEST_FILE_PREFIX) - names = get_module_names(package_dir, is_unittest_module) - names.sort() + names = get_module_names(package_dir=package_dir, should_include=is_unittest_module) # This is a sanity check to ensure that the unit-test discovery # methods are working. -- cgit v1.2.1 From 3313e1f5c559c507d620255fe56c60746297b811 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 13:21:13 -0700 Subject: Added unit tests for pystache/__init__.py. --- pystache/__init__.py | 9 ++++----- pystache/init.py | 4 ---- pystache/tests/test___init__.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 pystache/tests/test___init__.py (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index eb3ee9f..f83739e 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -6,9 +6,8 @@ TODO: add a docstring. # We keep all initialization code in a separate module. # TODO: consider doing something like this instead: -# from pystache.init import __version__, render, Renderer, TemplateSpec -from pystache.init import * +from pystache.init import render, Renderer, TemplateSpec -# TODO: make sure that "from pystache import *" exposes only the following: -# ['__version__', 'render', 'Renderer', 'TemplateSpec'] -# and add a unit test for this. +__all__ = ['render', 'Renderer', 'TemplateSpec'] + +__version__ = '0.5.1-alpha' # Also change in setup.py. diff --git a/pystache/init.py b/pystache/init.py index ba8b709..e9d854d 100644 --- a/pystache/init.py +++ b/pystache/init.py @@ -9,10 +9,6 @@ from pystache.renderer import Renderer from pystache.template_spec import TemplateSpec -__all__ = ['__version__', 'render', 'Renderer', 'TemplateSpec'] - -__version__ = '0.5.1-alpha' # Also change in setup.py. - def render(template, context=None, **kwargs): """ Return the given template string rendered using the given context. diff --git a/pystache/tests/test___init__.py b/pystache/tests/test___init__.py new file mode 100644 index 0000000..d4f3526 --- /dev/null +++ b/pystache/tests/test___init__.py @@ -0,0 +1,36 @@ +# coding: utf-8 + +""" +Tests of __init__.py. + +""" + +# Calling "import *" is allowed only at the module level. +GLOBALS_INITIAL = globals().keys() +from pystache import * +GLOBALS_PYSTACHE_IMPORTED = globals().keys() + +import unittest + +import pystache + + +class InitTests(unittest.TestCase): + + def test___all__(self): + """ + Test that "from pystache import *" works as expected. + + """ + actual = set(GLOBALS_PYSTACHE_IMPORTED) - set(GLOBALS_INITIAL) + expected = set(['render', 'Renderer', 'TemplateSpec', 'GLOBALS_INITIAL']) + + self.assertEqual(actual, expected) + + def test_version_defined(self): + """ + Test that pystache.__version__ is set. + + """ + actual_version = pystache.__version__ + self.assertTrue(actual_version) -- cgit v1.2.1 From e05966ee30144a624f88f94ac615e42ff4c5a8ac Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 13:22:03 -0700 Subject: Removed a TODO that has already been addressed. --- pystache/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index f83739e..c4a65dc 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -5,7 +5,7 @@ TODO: add a docstring. """ # We keep all initialization code in a separate module. -# TODO: consider doing something like this instead: + from pystache.init import render, Renderer, TemplateSpec __all__ = ['render', 'Renderer', 'TemplateSpec'] -- cgit v1.2.1 From f30aad9ad2a3fd6be307d76c719d13f73d62734e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 17:45:04 -0700 Subject: Changes in preparation for 0.5.1. --- pystache/tests/main.py | 18 +++++++++++++++++- pystache/tests/spectesting.py | 12 ++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 9364f39..c75664e 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -9,15 +9,16 @@ This module is for our test console script. import os import sys +import unittest from unittest import TestProgram +import pystache from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, SPEC_TEST_DIR, UNITTEST_FILE_PREFIX from pystache.tests.common import get_module_names from pystache.tests.doctesting import get_doctests from pystache.tests.spectesting import get_spec_tests -# TODO: enhance this function to create spec-test tests. def run_tests(sys_argv): """ Run all tests in the project. @@ -46,6 +47,8 @@ def run_tests(sys_argv): # auto-detect all unit tests. module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) + # Add the current module for unit tests contained here. + sys_argv.append(__name__) _PystacheTestProgram._text_doctest_dir = project_dir _PystacheTestProgram._spec_test_dir = spec_test_dir @@ -78,6 +81,19 @@ def _discover_test_modules(package_dir): return names +class SetupTests(unittest.TestCase): + + """Tests about setup.py.""" + + def test_version(self): + """ + Test that setup.py's version matches the package's version. + + """ + from setup import VERSION + self.assertEqual(VERSION, pystache.__version__) + + # The function unittest.main() is an alias for unittest.TestProgram's # constructor. TestProgram's constructor calls self.runTests() as its # final step, which expects self.test to be set. The constructor sets diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py index cb9d316..d79d75c 100644 --- a/pystache/tests/spectesting.py +++ b/pystache/tests/spectesting.py @@ -61,6 +61,9 @@ def get_spec_tests(spec_test_dir): Return a list of unittest.TestCase instances. """ + # TODO: use logging module instead. + print "pystache: spec tests: using %s" % _get_parser_info() + cases = [] # Make this absolute for easier diagnosis in case of error. @@ -90,6 +93,10 @@ def get_spec_tests(spec_test_dir): return cases +def _get_parser_info(): + return "%s (version %s)" % (parser.__name__, parser.__version__) + + def _read_spec_tests(path): """ Return a list of unittest.TestCase instances. @@ -231,8 +238,9 @@ class SpecTestBase(unittest.TestCase, AssertStringMixin): def escape(s): return s.replace("%", "%%") + parser_info = _get_parser_info() subs = [repr(test_name), description, os.path.abspath(file_path), - template, repr(context), parser.__version__, str(parser)] + template, repr(context), parser_info] subs = tuple([escape(sub) for sub in subs]) # We include the parsing module version info to help with troubleshooting # yaml/json/simplejson issues. @@ -246,7 +254,7 @@ class SpecTestBase(unittest.TestCase, AssertStringMixin): %%s - (using version %s of %s) + [using %s] """ % subs self.assertString(actual, expected, format=message) -- cgit v1.2.1 From 9447214e6bd58376bf12c29b1d9e929a0f827d60 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Mon, 23 Apr 2012 18:27:47 -0700 Subject: Fixed an issue whereby setup.py was not importable when running tox. --- pystache/tests/main.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index c75664e..bcc30cc 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -52,6 +52,7 @@ def run_tests(sys_argv): _PystacheTestProgram._text_doctest_dir = project_dir _PystacheTestProgram._spec_test_dir = spec_test_dir + SetupTests.project_dir = project_dir # We pass None for the module because we do not want the unittest # module to resolve module names relative to a given module. @@ -85,13 +86,22 @@ class SetupTests(unittest.TestCase): """Tests about setup.py.""" + project_dir = None + def test_version(self): """ Test that setup.py's version matches the package's version. """ - from setup import VERSION - self.assertEqual(VERSION, pystache.__version__) + original_path = list(sys.path) + + sys.path.insert(0, self.project_dir) + + try: + from setup import VERSION + self.assertEqual(VERSION, pystache.__version__) + finally: + sys.path = original_path # The function unittest.main() is an alias for unittest.TestProgram's -- cgit v1.2.1 From ff36b627772bc37263093f2d01fb08e699472c75 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 24 Apr 2012 03:24:42 -0700 Subject: Test scripts now work with and without source. --- pystache/tests/main.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/main.py b/pystache/tests/main.py index bcc30cc..7342c91 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -19,6 +19,11 @@ from pystache.tests.doctesting import get_doctests from pystache.tests.spectesting import get_spec_tests +# If this command option is present, then the spec test and doctest directories +# will be inserted if not provided. +FROM_SOURCE_OPTION = "--from-source" + + def run_tests(sys_argv): """ Run all tests in the project. @@ -28,27 +33,41 @@ def run_tests(sys_argv): sys_argv: a reference to sys.argv. """ + should_source_exist = False + spec_test_dir = None + project_dir = None + + if len(sys_argv) > 1 and sys_argv[1] == FROM_SOURCE_OPTION: + should_source_exist = True + sys_argv.pop(1) + + # TODO: use logging module + print "pystache: running tests: expecting source: %s" % should_source_exist + try: # TODO: use optparse command options instead. - project_dir = sys_argv[1] + spec_test_dir = sys_argv[1] sys_argv.pop(1) except IndexError: - project_dir = PROJECT_DIR + if should_source_exist: + spec_test_dir = SPEC_TEST_DIR try: # TODO: use optparse command options instead. - spec_test_dir = sys_argv[1] + project_dir = sys_argv[1] sys_argv.pop(1) except IndexError: - spec_test_dir = SPEC_TEST_DIR + if should_source_exist: + project_dir = PROJECT_DIR if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"): # Then no explicit module or test names were provided, so # auto-detect all unit tests. module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) - # Add the current module for unit tests contained here. - sys_argv.append(__name__) + if project_dir is not None: + # Add the current module for unit tests contained here. + sys_argv.append(__name__) _PystacheTestProgram._text_doctest_dir = project_dir _PystacheTestProgram._spec_test_dir = spec_test_dir @@ -123,10 +142,12 @@ class _PystacheTestProgram(TestProgram): # http://docs.python.org/library/unittest.html#unittest.TestSuite tests = self.test - doctest_suites = get_doctests(self._text_doctest_dir) - tests.addTests(doctest_suites) + if self._text_doctest_dir is not None: + doctest_suites = get_doctests(self._text_doctest_dir) + tests.addTests(doctest_suites) - spec_testcases = get_spec_tests(self._spec_test_dir) - tests.addTests(spec_testcases) + if self._spec_test_dir is not None: + spec_testcases = get_spec_tests(self._spec_test_dir) + tests.addTests(spec_testcases) TestProgram.runTests(self) -- cgit v1.2.1 From 8d02bb2560f72b35fd64753e8c7ce85a61003f20 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 24 Apr 2012 03:34:53 -0700 Subject: Finalizing v0.5.1 prior to merge to master, bump version to 0.5.1-rc. --- pystache/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index c4a65dc..bb50cde 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -10,4 +10,4 @@ from pystache.init import render, Renderer, TemplateSpec __all__ = ['render', 'Renderer', 'TemplateSpec'] -__version__ = '0.5.1-alpha' # Also change in setup.py. +__version__ = '0.5.1-rc' # Also change in setup.py. -- cgit v1.2.1 From 59a0d80911b4632625007a0959c6637f29228c3f Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 24 Apr 2012 23:14:37 -0700 Subject: Refactored by adding a resolve() function to context.py: The resolve() function is responsible for the interpolation name resolution rules described in the Mustache spec. Also, falsey values are now coerced to strings using Python's str(), which is more in line with the spec. --- pystache/context.py | 21 ++++++++++++++++++++- pystache/renderengine.py | 14 ++++---------- pystache/tests/examples/simple.py | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) (limited to 'pystache') diff --git a/pystache/context.py b/pystache/context.py index a8f3964..32f1bc4 100644 --- a/pystache/context.py +++ b/pystache/context.py @@ -1,7 +1,7 @@ # coding: utf-8 """ -Defines a Context class to represent mustache(5)'s notion of context. +Exposes a context class and functions to retrieve names from context. """ @@ -60,6 +60,25 @@ def _get_value(item, key): return _NOT_FOUND +# TODO: add some unit tests for this. +def resolve(context, name): + """ + Resolve the given name against the given context stack. + + This function follows the rules outlined in the section of the spec + regarding tag interpolation. + + This function does not coerce the return value to a string. + + """ + if name == '.': + return context.top() + + # The spec says that if the name fails resolution, the result should be + # considered falsey, and should interpolate as the empty string. + return context.get(name, '') + + class Context(object): """ diff --git a/pystache/renderengine.py b/pystache/renderengine.py index d9c822c..f723c32 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -7,6 +7,7 @@ Defines a class responsible for rendering logic. import re +from pystache.context import resolve from pystache.parser import Parser @@ -68,16 +69,7 @@ class RenderEngine(object): Get a value from the given context as a basestring instance. """ - val = context.get(tag_name) - - # We use "==" rather than "is" to compare integers, as using "is" - # relies on an implementation detail of CPython. The test about - # rendering zeroes failed while using PyPy when using "is". - # See issue #34: https://github.com/defunkt/pystache/issues/34 - if not val and val != 0: - if tag_name != '.': - return '' - val = context.top() + val = resolve(context, tag_name) if callable(val): # According to the spec: @@ -142,6 +134,8 @@ class RenderEngine(object): Returns a string with type unicode. """ + # TODO: is there a bug because we are not using the same + # logic as in _get_string_value()? data = context.get(name) if data: return u'' diff --git a/pystache/tests/examples/simple.py b/pystache/tests/examples/simple.py index 4e611d0..ea82e9d 100644 --- a/pystache/tests/examples/simple.py +++ b/pystache/tests/examples/simple.py @@ -12,4 +12,4 @@ class Simple(TemplateSpec): return "pizza" def blank(self): - pass + return '' -- cgit v1.2.1 From 340c2991635e1c4330ebd329260de56754a01ec6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 25 Apr 2012 18:45:07 -0700 Subject: Renamed Context to ContextStack. --- pystache/context.py | 36 +++++++++---------- pystache/parsed.py | 2 +- pystache/renderengine.py | 4 +-- pystache/renderer.py | 6 ++-- pystache/tests/test_context.py | 70 ++++++++++++++++++------------------- pystache/tests/test_renderengine.py | 4 +-- 6 files changed, 61 insertions(+), 61 deletions(-) (limited to 'pystache') diff --git a/pystache/context.py b/pystache/context.py index 32f1bc4..de22a75 100644 --- a/pystache/context.py +++ b/pystache/context.py @@ -1,7 +1,7 @@ # coding: utf-8 """ -Exposes a context class and functions to retrieve names from context. +Exposes a ContextStack class and functions to retrieve names from context. """ @@ -33,7 +33,7 @@ def _get_value(item, key): Returns _NOT_FOUND if the key does not exist. - The Context.get() docstring documents this function's intended behavior. + The ContextStack.get() docstring documents this function's intended behavior. """ if isinstance(item, dict): @@ -79,7 +79,7 @@ def resolve(context, name): return context.get(name, '') -class Context(object): +class ContextStack(object): """ Provides dictionary-like access to a stack of zero or more items. @@ -94,7 +94,7 @@ class Context(object): (last in, first out). Caution: this class does not currently support recursive nesting in - that items in the stack cannot themselves be Context instances. + that items in the stack cannot themselves be ContextStack instances. See the docstrings of the methods of this class for more details. @@ -111,7 +111,7 @@ class Context(object): stack in order so that, in particular, items at the end of the argument list are queried first when querying the stack. - Caution: items should not themselves be Context instances, as + Caution: items should not themselves be ContextStack instances, as recursive nesting does not behave as one might expect. """ @@ -123,9 +123,9 @@ class Context(object): For example-- - >>> context = Context({'alpha': 'abc'}, {'numeric': 123}) + >>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123}) >>> repr(context) - "Context({'alpha': 'abc'}, {'numeric': 123})" + "ContextStack({'alpha': 'abc'}, {'numeric': 123})" """ return "%s%s" % (self.__class__.__name__, tuple(self._stack)) @@ -133,18 +133,18 @@ class Context(object): @staticmethod def create(*context, **kwargs): """ - Build a Context instance from a sequence of context-like items. + Build a ContextStack instance from a sequence of context-like items. - This factory-style method is more general than the Context class's + This factory-style method is more general than the ContextStack class's constructor in that, unlike the constructor, the argument list - can itself contain Context instances. + can itself contain ContextStack instances. Here is an example illustrating various aspects of this method: >>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'} - >>> obj2 = Context({'vegetable': 'spinach', 'mineral': 'silver'}) + >>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'}) >>> - >>> context = Context.create(obj1, None, obj2, mineral='gold') + >>> context = ContextStack.create(obj1, None, obj2, mineral='gold') >>> >>> context.get('animal') 'cat' @@ -155,7 +155,7 @@ class Context(object): Arguments: - *context: zero or more dictionaries, Context instances, or objects + *context: zero or more dictionaries, ContextStack instances, or objects with which to populate the initial context stack. None arguments will be skipped. Items in the *context list are added to the stack in order so that later items in the argument @@ -171,12 +171,12 @@ class Context(object): """ items = context - context = Context() + context = ContextStack() for item in items: if item is None: continue - if isinstance(item, Context): + if isinstance(item, ContextStack): context._stack.extend(item._stack) else: context.push(item) @@ -245,9 +245,9 @@ class Context(object): >>> >>> dct['greet'] is obj.greet True - >>> Context(dct).get('greet') #doctest: +ELLIPSIS + >>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS - >>> Context(obj).get('greet') + >>> ContextStack(obj).get('greet') 'Hi Bob!' TODO: explain the rationale for this difference in treatment. @@ -289,4 +289,4 @@ class Context(object): Return a copy of this instance. """ - return Context(*self._stack) + return ContextStack(*self._stack) diff --git a/pystache/parsed.py b/pystache/parsed.py index 5418ec1..552af55 100644 --- a/pystache/parsed.py +++ b/pystache/parsed.py @@ -17,7 +17,7 @@ class ParsedTemplate(object): parse_tree: a list, each element of which is either-- (1) a unicode string, or - (2) a "rendering" callable that accepts a Context instance + (2) a "rendering" callable that accepts a ContextStack instance and returns a unicode string. The possible rendering callables are the return values of the diff --git a/pystache/renderengine.py b/pystache/renderengine.py index f723c32..25fccd9 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -215,7 +215,7 @@ class RenderEngine(object): Arguments: template: a template string of type unicode. - context: a Context instance. + context: a ContextStack instance. """ # We keep this type-check as an added check because this method is @@ -238,7 +238,7 @@ class RenderEngine(object): template: a template string of type unicode (but not a proper subclass of unicode). - context: a Context instance. + context: a ContextStack instance. """ # Be strict but not too strict. In other words, accept str instead diff --git a/pystache/renderer.py b/pystache/renderer.py index d55c72f..26f271f 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -8,7 +8,7 @@ This module provides a Renderer class to render templates. import sys from pystache import defaults -from pystache.context import Context +from pystache.context import ContextStack from pystache.loader import Loader from pystache.renderengine import RenderEngine from pystache.specloader import SpecLoader @@ -277,7 +277,7 @@ class Renderer(object): # RenderEngine.render() requires that the template string be unicode. template = self._to_unicode_hard(template) - context = Context.create(*context, **kwargs) + context = ContextStack.create(*context, **kwargs) self._context = context engine = self._make_render_engine() @@ -338,7 +338,7 @@ class Renderer(object): uses the passed object as the first element of the context stack when rendering. - *context: zero or more dictionaries, Context instances, or objects + *context: zero or more dictionaries, ContextStack instances, or objects with which to populate the initial context stack. None arguments are skipped. Items in the *context list are added to the context stack in order so that later items in the argument diff --git a/pystache/tests/test_context.py b/pystache/tests/test_context.py index 9856fee..dd9fdae 100644 --- a/pystache/tests/test_context.py +++ b/pystache/tests/test_context.py @@ -10,7 +10,7 @@ import unittest from pystache.context import _NOT_FOUND from pystache.context import _get_value -from pystache.context import Context +from pystache.context import ContextStack from pystache.tests.common import AssertIsMixin class SimpleObject(object): @@ -204,10 +204,10 @@ class GetValueTests(unittest.TestCase, AssertIsMixin): self.assertNotFound(item2, 'pop') -class ContextTests(unittest.TestCase, AssertIsMixin): +class ContextStackTests(unittest.TestCase, AssertIsMixin): """ - Test the Context class. + Test the ContextStack class. """ @@ -216,34 +216,34 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Check that passing nothing to __init__() raises no exception. """ - context = Context() + context = ContextStack() def test_init__many_elements(self): """ Check that passing more than two items to __init__() raises no exception. """ - context = Context({}, {}, {}) + context = ContextStack({}, {}, {}) def test__repr(self): - context = Context() - self.assertEqual(repr(context), 'Context()') + context = ContextStack() + self.assertEqual(repr(context), 'ContextStack()') - context = Context({'foo': 'bar'}) - self.assertEqual(repr(context), "Context({'foo': 'bar'},)") + context = ContextStack({'foo': 'bar'}) + self.assertEqual(repr(context), "ContextStack({'foo': 'bar'},)") - context = Context({'foo': 'bar'}, {'abc': 123}) - self.assertEqual(repr(context), "Context({'foo': 'bar'}, {'abc': 123})") + context = ContextStack({'foo': 'bar'}, {'abc': 123}) + self.assertEqual(repr(context), "ContextStack({'foo': 'bar'}, {'abc': 123})") def test__str(self): - context = Context() - self.assertEqual(str(context), 'Context()') + context = ContextStack() + self.assertEqual(str(context), 'ContextStack()') - context = Context({'foo': 'bar'}) - self.assertEqual(str(context), "Context({'foo': 'bar'},)") + context = ContextStack({'foo': 'bar'}) + self.assertEqual(str(context), "ContextStack({'foo': 'bar'},)") - context = Context({'foo': 'bar'}, {'abc': 123}) - self.assertEqual(str(context), "Context({'foo': 'bar'}, {'abc': 123})") + context = ContextStack({'foo': 'bar'}, {'abc': 123}) + self.assertEqual(str(context), "ContextStack({'foo': 'bar'}, {'abc': 123})") ## Test the static create() method. @@ -252,7 +252,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test passing a dictionary. """ - context = Context.create({'foo': 'bar'}) + context = ContextStack.create({'foo': 'bar'}) self.assertEqual(context.get('foo'), 'bar') def test_create__none(self): @@ -260,7 +260,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test passing None. """ - context = Context.create({'foo': 'bar'}, None) + context = ContextStack.create({'foo': 'bar'}, None) self.assertEqual(context.get('foo'), 'bar') def test_create__object(self): @@ -270,16 +270,16 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ class Foo(object): foo = 'bar' - context = Context.create(Foo()) + context = ContextStack.create(Foo()) self.assertEqual(context.get('foo'), 'bar') def test_create__context(self): """ - Test passing a Context instance. + Test passing a ContextStack instance. """ - obj = Context({'foo': 'bar'}) - context = Context.create(obj) + obj = ContextStack({'foo': 'bar'}) + context = ContextStack.create(obj) self.assertEqual(context.get('foo'), 'bar') def test_create__kwarg(self): @@ -287,7 +287,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test passing a keyword argument. """ - context = Context.create(foo='bar') + context = ContextStack.create(foo='bar') self.assertEqual(context.get('foo'), 'bar') def test_create__precedence_positional(self): @@ -295,7 +295,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test precedence of positional arguments. """ - context = Context.create({'foo': 'bar'}, {'foo': 'buzz'}) + context = ContextStack.create({'foo': 'bar'}, {'foo': 'buzz'}) self.assertEqual(context.get('foo'), 'buzz') def test_create__precedence_keyword(self): @@ -303,7 +303,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test precedence of keyword arguments. """ - context = Context.create({'foo': 'bar'}, foo='buzz') + context = ContextStack.create({'foo': 'bar'}, foo='buzz') self.assertEqual(context.get('foo'), 'buzz') def test_get__key_present(self): @@ -311,7 +311,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test getting a key. """ - context = Context({"foo": "bar"}) + context = ContextStack({"foo": "bar"}) self.assertEqual(context.get("foo"), "bar") def test_get__key_missing(self): @@ -319,7 +319,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test getting a missing key. """ - context = Context() + context = ContextStack() self.assertTrue(context.get("foo") is None) def test_get__default(self): @@ -327,7 +327,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test that get() respects the default value. """ - context = Context() + context = ContextStack() self.assertEqual(context.get("foo", "bar"), "bar") def test_get__precedence(self): @@ -335,7 +335,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Test that get() respects the order of precedence (later items first). """ - context = Context({"foo": "bar"}, {"foo": "buzz"}) + context = ContextStack({"foo": "bar"}, {"foo": "buzz"}) self.assertEqual(context.get("foo"), "buzz") def test_get__fallback(self): @@ -343,7 +343,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): Check that first-added stack items are queried on context misses. """ - context = Context({"fuzz": "buzz"}, {"foo": "bar"}) + context = ContextStack({"fuzz": "buzz"}, {"foo": "bar"}) self.assertEqual(context.get("fuzz"), "buzz") def test_push(self): @@ -352,7 +352,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ key = "foo" - context = Context({key: "bar"}) + context = ContextStack({key: "bar"}) self.assertEqual(context.get(key), "bar") context.push({key: "buzz"}) @@ -364,7 +364,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): """ key = "foo" - context = Context({key: "bar"}, {key: "buzz"}) + context = ContextStack({key: "bar"}, {key: "buzz"}) self.assertEqual(context.get(key), "buzz") item = context.pop() @@ -373,7 +373,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): def test_top(self): key = "foo" - context = Context({key: "bar"}, {key: "buzz"}) + context = ContextStack({key: "bar"}, {key: "buzz"}) self.assertEqual(context.get(key), "buzz") top = context.top() @@ -383,7 +383,7 @@ class ContextTests(unittest.TestCase, AssertIsMixin): def test_copy(self): key = "foo" - original = Context({key: "bar"}, {key: "buzz"}) + original = ContextStack({key: "bar"}, {key: "buzz"}) self.assertEqual(original.get(key), "buzz") new = original.copy() diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index 3c63cfb..d1f1a1d 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -7,7 +7,7 @@ Unit tests of renderengine.py. import unittest -from pystache.context import Context +from pystache.context import ContextStack from pystache import defaults from pystache.parser import ParsingError from pystache.renderengine import RenderEngine @@ -83,7 +83,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): if partials is not None: engine.load_partial = lambda key: unicode(partials[key]) - context = Context(*context) + context = ContextStack(*context) actual = engine.render(template, context) -- cgit v1.2.1 From 39a0d59144fb53f623054730d6e0c13136b9a7f1 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 25 Apr 2012 18:57:55 -0700 Subject: Add RenderEngine test cases for "falsey" context values. --- pystache/tests/test_renderengine.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'pystache') diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index d1f1a1d..e5dbfcc 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -204,6 +204,27 @@ class RenderTests(unittest.TestCase, AssertStringMixin): context = {'test': '{{#hello}}'} self._assert_render(u'{{#hello}}', template, context) + ## Test interpolation with "falsey" values + # + # In these test cases, we test the part of the spec that says that + # "data should be coerced into a string (and escaped, if appropriate) + # before interpolation." We test this for data that is "falsey." + + def test_interpolation__falsey__zero(self): + template = '{{.}}' + context = 0 + self._assert_render(u'0', template, context) + + def test_interpolation__falsey__none(self): + template = '{{.}}' + context = None + self._assert_render(u'None', template, context) + + def test_interpolation__falsey__zero(self): + template = '{{.}}' + context = False + self._assert_render(u'False', template, context) + # Built-in types: # # Confirm that we not treat instances of built-in types as objects, -- cgit v1.2.1 From cc262abf19cd90e34390d5ddb5db30d6f04620fa Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 25 Apr 2012 22:49:19 -0700 Subject: Bump version from 0.5.1-rc to 0.5.1 prior to release to PyPI. --- pystache/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/__init__.py b/pystache/__init__.py index bb50cde..5f5035d 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -10,4 +10,4 @@ from pystache.init import render, Renderer, TemplateSpec __all__ = ['render', 'Renderer', 'TemplateSpec'] -__version__ = '0.5.1-rc' # Also change in setup.py. +__version__ = '0.5.1' # Also change in setup.py. -- cgit v1.2.1 From 91a2f860b14e481357972c98c4997c18ef37d7d5 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 28 Apr 2012 10:26:39 -0700 Subject: Stubbed out tests/test_parser.py. --- pystache/tests/test_parser.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pystache/tests/test_parser.py (limited to 'pystache') diff --git a/pystache/tests/test_parser.py b/pystache/tests/test_parser.py new file mode 100644 index 0000000..c7bdafa --- /dev/null +++ b/pystache/tests/test_parser.py @@ -0,0 +1,26 @@ +# coding: utf-8 + +""" +Unit tests of parser.py. + +""" + +import unittest + +from pystache.parser import _compile_template_re as make_re + + +class RegularExpressionTestCase(unittest.TestCase): + + """Tests the regular expression returned by _compile_template_re().""" + + def test_re(self): + """ + Test getting a key from a dictionary. + + """ + re = make_re() + match = re.search("{{test}}") + + self.assertEqual(match.start(), 0) + -- cgit v1.2.1 From f7f63aea20b9d6528a7c6db66ef710037d4416a5 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 28 Apr 2012 10:36:06 -0700 Subject: Added back using nose to test a subset of tests. --- pystache/commands/test.py | 2 +- pystache/tests/main.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'pystache') diff --git a/pystache/commands/test.py b/pystache/commands/test.py index aec1ff6..0872453 100644 --- a/pystache/commands/test.py +++ b/pystache/commands/test.py @@ -7,7 +7,7 @@ This module provides a command to test pystache (unit tests, doctests, etc). import sys -from pystache.tests.main import run_tests +from pystache.tests.main import main as run_tests def main(sys_argv=sys.argv): diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 7342c91..de56c44 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -24,7 +24,9 @@ from pystache.tests.spectesting import get_spec_tests FROM_SOURCE_OPTION = "--from-source" -def run_tests(sys_argv): +# Do not include "test" in this function's name to avoid it getting +# picked up by nosetests. +def main(sys_argv): """ Run all tests in the project. -- cgit v1.2.1 From 337b6344abbfe758fc8c626fa7f1a374cf91787a Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 28 Apr 2012 11:28:15 -0700 Subject: Commented some of the section parsing code. --- pystache/parser.py | 57 ++++++++++++++++++++++++++++++++++--------- pystache/tests/test_parser.py | 4 +-- 2 files changed, 48 insertions(+), 13 deletions(-) (limited to 'pystache') diff --git a/pystache/parser.py b/pystache/parser.py index ccb6827..21eea8d 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -17,7 +17,13 @@ END_OF_LINE_CHARACTERS = ['\r', '\n'] NON_BLANK_RE = re.compile(r'^(.)', re.M) -def _compile_template_re(delimiters): +def _compile_template_re(delimiters=None): + """ + Return a regular expresssion object (re.RegexObject) instance. + + """ + if delimiters is None: + delimiters = DEFAULT_DELIMITERS # The possible tag type characters following the opening tag, # excluding "=" and "{". @@ -74,19 +80,25 @@ class Parser(object): self._delimiters = delimiters self.compile_template_re() - def parse(self, template, index=0, section_key=None): + def parse(self, template, start_index=0, section_key=None): """ - Parse a template string into a ParsedTemplate instance. + Parse a template string starting at some index. This method uses the current tag delimiter. Arguments: - template: a template string of type unicode. + template: a unicode string that is the template to parse. + + index: the index at which to start parsing. + + Returns: + + a ParsedTemplate instance. """ parse_tree = [] - start_index = index + index = start_index while True: match = self._template_re.search(template, index) @@ -142,10 +154,33 @@ class Parser(object): return ParsedTemplate(parse_tree) - def _parse_section(self, template, index_start, section_key): - parsed_template, template, index_end = self.parse(template=template, index=index_start, section_key=section_key) + def _parse_section(self, template, start_index, section_key): + """ + Parse the contents of a template section. + + Arguments: + + template: a unicode template string. + + start_index: the string index at which the section contents begin. + + section_key: the tag key of the section. + + Returns: a 3-tuple: + + parsed_section: the section contents parsed as a ParsedTemplate + instance. + + section_contents: the unparsed section contents. + + end_index: the string index after the closing section tag (and + including any trailing newlines). + + """ + parsed_section, section_contents, end_index = \ + self.parse(template=template, start_index=start_index, section_key=section_key) - return parsed_template, template, index_end + return parsed_section, section_contents, end_index def _handle_tag_type(self, template, parse_tree, tag_type, tag_key, leading_whitespace, end_index): @@ -170,12 +205,12 @@ class Parser(object): elif tag_type == '#': - parsed_section, template, end_index = self._parse_section(template, end_index, tag_key) - func = engine._make_get_section(tag_key, parsed_section, template, self._delimiters) + parsed_section, section_contents, end_index = self._parse_section(template, end_index, tag_key) + func = engine._make_get_section(tag_key, parsed_section, section_contents, self._delimiters) elif tag_type == '^': - parsed_section, template, end_index = self._parse_section(template, end_index, tag_key) + parsed_section, section_contents, end_index = self._parse_section(template, end_index, tag_key) func = engine._make_get_inverse(tag_key, parsed_section) elif tag_type == '>': diff --git a/pystache/tests/test_parser.py b/pystache/tests/test_parser.py index c7bdafa..4aa0959 100644 --- a/pystache/tests/test_parser.py +++ b/pystache/tests/test_parser.py @@ -20,7 +20,7 @@ class RegularExpressionTestCase(unittest.TestCase): """ re = make_re() - match = re.search("{{test}}") + match = re.search("b {{test}}") - self.assertEqual(match.start(), 0) + self.assertEqual(match.start(), 1) -- cgit v1.2.1 From 2adac861e22c29e1964c46a943c12e1b018c16cb Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 28 Apr 2012 11:40:12 -0700 Subject: Moved a string slice operation from parse() to _parse_section(). --- pystache/parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'pystache') diff --git a/pystache/parser.py b/pystache/parser.py index 21eea8d..2b97405 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -145,7 +145,7 @@ class Parser(object): if tag_key != section_key: raise ParsingError("Section end tag mismatch: %s != %s" % (tag_key, section_key)) - return ParsedTemplate(parse_tree), template[start_index:match_index], end_index + return ParsedTemplate(parse_tree), match_index, end_index index = self._handle_tag_type(template, parse_tree, tag_type, tag_key, leading_whitespace, end_index) @@ -171,16 +171,16 @@ class Parser(object): parsed_section: the section contents parsed as a ParsedTemplate instance. - section_contents: the unparsed section contents. + content_end_index: the string index after the section contents. end_index: the string index after the closing section tag (and including any trailing newlines). """ - parsed_section, section_contents, end_index = \ + parsed_section, content_end_index, end_index = \ self.parse(template=template, start_index=start_index, section_key=section_key) - return parsed_section, section_contents, end_index + return parsed_section, template[start_index:content_end_index], end_index def _handle_tag_type(self, template, parse_tree, tag_type, tag_key, leading_whitespace, end_index): -- cgit v1.2.1 From 21ab97cdeac2b10799af05034d4070db242944a6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 29 Apr 2012 02:09:20 -0700 Subject: Fixed issue #113: "Section lambdas mistakenly pushed onto context stack" --- pystache/renderengine.py | 7 ++++++- pystache/tests/test_renderengine.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) (limited to 'pystache') diff --git a/pystache/renderengine.py b/pystache/renderengine.py index 25fccd9..9e4da11 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -161,7 +161,12 @@ class RenderEngine(object): # TODO: should we check the arity? template = data(template) parsed_template = self._parse(template, delimiters=delims) - data = [data] + # Lambdas special case section rendering and bypass pushing + # the data value onto the context stack. Also see-- + # + # https://github.com/defunkt/pystache/issues/113 + # + return parsed_template.render(context) else: # The cleanest, least brittle way of determining whether # something supports iteration is by trying to call iter() on it: diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index e5dbfcc..cd8b191 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -501,6 +501,40 @@ class RenderTests(unittest.TestCase, AssertStringMixin): context = {'person': 'Mom', 'test': (lambda text: text + " :)")} self._assert_render(u'Hi Mom :)', template, context) + def test_section__lambda__not_on_context_stack(self): + """ + Check that section lambdas are not pushed onto the context stack. + + Even though the sections spec says that section data values should be + pushed onto the context stack prior to rendering, this does not apply + to lambdas. Lambdas obey their own special case. + + This test case is equivalent to a test submitted to the Mustache spec here: + + https://github.com/mustache/spec/pull/47 . + + """ + context = {'foo': 'bar', 'lambda': (lambda text: "{{.}}")} + template = '{{#foo}}{{#lambda}}blah{{/lambda}}{{/foo}}' + self._assert_render(u'bar', template, context) + + def test_section__lambda__no_reinterpolation(self): + """ + Check that section lambda return values are not re-interpolated. + + This test is a sanity check that the rendered lambda return value + is not re-interpolated as could be construed by reading the + section part of the Mustache spec. + + This test case is equivalent to a test submitted to the Mustache spec here: + + https://github.com/mustache/spec/pull/47 . + + """ + template = '{{#planet}}{{#lambda}}dot{{/lambda}}{{/planet}}' + context = {'planet': 'Earth', 'dot': '~{{.}}~', 'lambda': (lambda text: "#{{%s}}#" % text)} + self._assert_render(u'#~{{.}}~#', template, context) + def test_comment__multiline(self): """ Check that multiline comments are permitted. -- cgit v1.2.1 From 54eb4b5047bc75a596c9734f460726aa462aea2e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sun, 29 Apr 2012 02:12:52 -0700 Subject: Refactored spectesting.py: added _convert_children() function. --- pystache/tests/spectesting.py | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) (limited to 'pystache') diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py index d79d75c..ec8a08d 100644 --- a/pystache/tests/spectesting.py +++ b/pystache/tests/spectesting.py @@ -115,6 +115,37 @@ def _read_spec_tests(path): return cases +# TODO: simplify the implementation of this function. +def _convert_children(node): + """ + Recursively convert to functions all "code strings" below the node. + + This function is needed only for the json format. + + """ + if not isinstance(node, (list, dict)): + # Then there is nothing to iterate over and recurse. + return + + if isinstance(node, list): + for child in node: + _convert_children(child) + return + # Otherwise, node is a dict, so attempt the conversion. + + for key in node.keys(): + val = node[key] + + if not isinstance(val, dict) or val.get('__tag__') != 'code': + _convert_children(val) + continue + # Otherwise, we are at a "leaf" node. + + val = eval(val['python']) + node[key] = val + continue + + def _deserialize_spec_test(data, file_path): """ Return a unittest.TestCase instance representing a spec test. @@ -124,7 +155,7 @@ def _deserialize_spec_test(data, file_path): data: the dictionary of attributes for a single test. """ - unconverted_context = data['data'] + context = data['data'] description = data['desc'] # PyYAML seems to leave ASCII strings as byte strings. expected = unicode(data['expected']) @@ -133,13 +164,7 @@ def _deserialize_spec_test(data, file_path): template = data['template'] test_name = data['name'] - # Convert code strings to functions. - # TODO: make this section of code easier to understand. - context = {} - for key, val in unconverted_context.iteritems(): - if isinstance(val, dict) and val.get('__tag__') == 'code': - val = eval(val['python']) - context[key] = val + _convert_children(context) test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path) -- cgit v1.2.1