[Moin-user] Patch: tests for Include macro

Greg Ward gerg.ward+moin at gmail.com
Tue Oct 28 17:29:52 EDT 2008


Hi all --

a week or two ago, I made vague noises on this list about refactoring
the Include macro and adding tests for it.  Well, I have made good
progress on the tests today: in fact, the attached patch adds a test
suite for Include that tests every feature *except* 'editlink'.  (I ran
out of time.  Will try to test that tomorrow.)

Tomorrow, I'll be a good citizen and publish a repository with this
patch, create a patch page in the MoinMoin wiki, etc.  For now, please
take a look at the attached tests and let me know what you think!

Thanks --

        Greg

P.S. this is relative to 1.8 and not quite ready to commit yet, but it's
a very good start.
-------------- next part --------------
changeset:   4182:bf9bca1e748e
tag:         qbase
tag:         tip
tag:         test-include
tag:         qtip
parent:      4178:0b9869467641
user:        gerg.ward+moin at gmail.com
date:        Tue Oct 28 17:25:18 2008 -0400
summary:     Add tests for Include macro.

diff -r 0b9869467641 -r bf9bca1e748e MoinMoin/macro/_tests/test_Include.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/_tests/test_Include.py	Tue Oct 28 17:25:18 2008 -0400
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+"""
+    MoinMoin - MoinMoin.macro.Include Tests
+
+    @copyright: 2008 MoinMoin:GregWard
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import re
+from MoinMoin._tests import become_trusted, create_page, make_macro, nuke_page
+
+class BaseTest:
+    def _execute(self, args):
+        """Execute macro Include in self.includer_page with args, returning the result."""
+        macro = make_macro(self.request, self.includer_page)
+        result = macro.execute('Include', args)
+        print "macro result: %r" % result
+        return result
+
+    def _assert_regex(self, expect_re, actual_result, pos):
+        print "expected result to match %s from pos %d" % (expect_re.pattern, pos)
+        match = expect_re.search(actual_result, pos=pos)
+        assert match is not None
+        return match
+
+    def _assert_included(self, expect_content, actual_result, pos=0):
+        # Hmmm: the space after the included content seems strange, but
+        # it is how Include behaves.
+        content_re = re.compile(r'<p class="\w+">%s<span' % re.escape(expect_content + " "))
+        return self._assert_regex(content_re, actual_result, pos=pos)
+
+    def _assert_sysmsg(self, expect_class, expect_message, actual_result):
+        msg_re = re.compile(r'<p><strong class="%s">%s</strong></p>'
+                            % (re.escape(expect_class), re.escape(expect_message)))
+        return self._assert_regex(msg_re, actual_result, pos=0)
+
+    def _assert_heading(self, expect_level, expect_content, actual_result, pos=0):
+        heading_re = re.compile(r'<h%d id=\"[\w\.\-]+\">%s</h%d>'
+                                % (expect_level, re.escape(expect_content), expect_level))
+        return self._assert_regex(heading_re, actual_result, pos=pos)
+
+    def _assert_link(self, expect_href, expect_text, actual_result, pos=0):
+        link_re = re.compile(r'<a href=\"%s\">%s</a>'
+                             % (expect_href, re.escape(expect_text)))
+        return self._assert_regex(link_re, actual_result, pos=pos)
+
+class TestInclude(BaseTest):
+    """tests for macro Include"""
+    included_pagename = u'IncludedPage'
+    included_content  = u'Here is included content!'
+    includer_pagename = u'IncluderPage'
+
+    def setup_method(self, method):
+        assert isinstance(self, TestInclude)
+
+        request = self.request
+        become_trusted(request)
+        self._create_included_page()
+        self.includer_page = create_page(
+            request, self.includer_pagename, u'Argh')
+
+    def teardown_method(self, method):
+        nuke_page(self.request, self.included_pagename)
+        nuke_page(self.request, self.includer_pagename)
+
+    def _create_included_page(self, pagename=included_pagename, content=included_content):
+        return create_page(
+            self.request, pagename, content)
+
+    def testIncludeSimple(self):
+        result = self._execute(u'IncludedPage')
+        self._assert_included(self.included_content, result)
+
+    def testIncludeNoArgs(self):
+        # with no args, Include() expands to an error message
+        result = self._execute(u'')
+        assert 'Invalid include arguments' in result
+        
+    def testIncludeMarkup(self):
+        # If the included page includes wiki markup, it is rendered to
+        # HTML by the macro.
+        # XXX this makes assumptions about the formatter: is that evil?
+        self._create_included_page(content=u'This is \'\'\'bold\'\'\' text')
+        result = self._execute(u'IncludedPage')
+        self._assert_included('This is <strong>bold</strong> text', result)
+
+    def testIncludeHeading(self):
+        # You can add a heading to the included content by passing 'heading'
+        # arg to Include.
+        heading = u'Flubby Flobby'
+        result = self._execute(u'IncludedPage, "%s"' % heading)
+
+        # XXX again, this makes assumptions about the formatter: evil?
+        heading_match = re.search(r'<h1 id="FlubbyFlobby">.*%s.*</h1>' % heading, result)
+        assert heading_match
+
+        content_idx = result.find(self.included_content)
+        assert content_idx > heading_match.end()
+
+    def testIncludeHeadingLevel(self):
+        # Add a heading with a specific level.
+        heading = u'Weeble Wobble'
+        result = self._execute(u'IncludedPage, "%s", 3' % heading)
+
+        # XXX again, this makes assumptions about the formatter: evil?
+        heading_match = re.search(r'<h3 id="WeebleWobble">.*%s.*</h3>' % heading, result)
+        assert heading_match
+
+    def testIncludeFrom(self):
+        # Start including at the point right after text that matches the 'from' regex.
+        included_content = 'Here is some --delimited-- content'
+        self._create_included_page(content=included_content)
+        result = self._execute(u'IncludedPage,,, from="-+"')
+
+        self._assert_included('delimited-- content', result)
+        assert "Here is some" not in result
+
+    def testIncludeFromBadRegex(self):
+        # If the user passes a bad "from" regex -- say, r"foo[" -- then
+        # Include assumes the user meant to escape the metacharacters
+        # and does it for them, changing the regex to r"foo\[".
+        included_content = u'start including after foo[that string].'
+        self._create_included_page(content=included_content)
+
+        # First: do it with the correct regex syntax, so Include does
+        # not have to catch re.error.
+        result1 = self._execute(u'IncludedPage,,, from="foo\\["')
+        self._assert_included(u'that string].', result1)
+
+        # Now trip the exception and see that Include escapes the "[".
+        result2 = self._execute(u'IncludedPage,,, from="foo["')
+        self._assert_included(u'that string].', result2)
+
+    def testIncludeFromNoMatch(self):
+        # If the 'from' regex does not match the included page, Include
+        # inserts a warning message and then completely includes the
+        # other page.
+        result = self._execute(u'IncludedPage,,, from="foo"')
+        self._assert_sysmsg('warning', 'Include: Nothing found for "foo"!', result)
+        self._assert_included(self.included_content, result)
+
+    # Sigh.  The tests for 'to' are very similar to the tests for
+    # 'from'.  But the code being tested is also quite repetitive, so
+    # it's important to test both 'from' and 'to' thoroughly before
+    # refactoring!
+
+    def testIncludeTo(self):
+        # Stop including at the point before after text that matches the 'to' regex.
+        included_content = 'Here is some --delimited-- content'
+        self._create_included_page(content=included_content)
+        result = self._execute(u'IncludedPage,,, to="-+"')
+
+        self._assert_included('Here is some ', result)
+        assert "--delimited-- content" not in result
+        
+    def testIncludeToBadRegex(self):
+        # As with 'from', a bad 'to' regex gets quoted and tried again.
+        included_content = u'include up to foo[that string'
+        self._create_included_page(content=included_content)
+
+        # First with correct regex syntax.
+        result1 = self._execute(u'IncludedPage,,, to="foo\\["')
+        self._assert_included(u'include up to ', result1)
+
+        # Now trip the exception and make sure we get the same thing.
+        result2 = self._execute(u'IncludedPage,,, to="foo["')
+        self._assert_included(u'include up to ', result2)
+
+    def testIncludeToNoMatch(self):
+        # As with 'from', a bad 'to' regex results in a warning.
+        result = self._execute(u'IncludedPage,,, to="foo"')
+        self._assert_sysmsg('warning', 'Include: Nothing found for "foo"!', result)
+        self._assert_included(self.included_content, result)
+
+class TestIncludeMany(BaseTest):
+    includer_pagename = u'IncluderPage'
+
+    included_pagename_1 = u'1_Intro'
+    included_content_1 = u'''\
+= Intro =
+
+This is an introduction.
+
+== Ooga ==
+
+Ooga-booga.  Ding-dong.
+
+== Argle ==
+
+Argle bargle snarky weeb.
+'''
+
+    included_pagename_2 = u'2_BlahBlah'
+    included_content_2 = u'''\
+= Blah Blah =
+
+more blah blah blah
+'''
+    included_pagename_3 = u'3_Conclusions'
+    included_content_3 = u'''\
+= Conclusions =
+
+Clearly, the snarky weeb rocks.
+'''
+
+    def setup_class(cls):
+        become_trusted(cls.request)
+        cls.includer_page = create_page(cls.request, cls.includer_pagename, u'Foo!')
+
+        create_page(cls.request, cls.included_pagename_1, cls.included_content_1)
+        create_page(cls.request, cls.included_pagename_2, cls.included_content_2)
+        create_page(cls.request, cls.included_pagename_3, cls.included_content_3)
+
+    def teardown_class(cls):
+        nuke_page(cls.request, cls.includer_pagename)
+        nuke_page(cls.request, cls.included_pagename_1)
+        nuke_page(cls.request, cls.included_pagename_2)
+        nuke_page(cls.request, cls.included_pagename_3)
+        
+    def testIncludeMany(self):
+        # Include with a regex pulls in multiple pages.
+        result = self._execute(u'^\d_')
+        match = self._assert_heading(1, 'Intro', result)
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        match = self._assert_heading(1, 'Conclusions', result, pos=match.end())
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+
+    def testIncludeManySortDescending(self):
+        # sort=descending includes pages in reverse order
+        result = self._execute(u'^\d_,,, sort=descending')
+        match = self._assert_heading(1, 'Conclusions', result)
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        match = self._assert_heading(1, 'Intro', result, pos=match.end())
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+        
+    def testIncludeManyMaxItems(self):
+        # items=N limits the include to N pages
+        result = self._execute(u'^\d_,,, items=2')
+        match = self._assert_heading(1, 'Intro', result)
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        assert 'Conclusions' not in result
+        assert 'Clearly, the snarky weeb rocks.' not in result
+
+    def testIncludeManySkipItems(self):
+        # skipitems=N skips the first N matching pages
+        result = self._execute(u'^\d_,,, skipitems=2')
+        match = self._assert_heading(1, 'Conclusions', result)
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+        assert 'Intro' not in result
+        assert 'Ooga-booga.  Ding-dong.' not in result
+        assert 'Blah Blah' not in result
+        assert 'more blah blah blah' not in result
+
+    def testIncludeManyTitlesOnly(self):
+        # titlesonly suppresses content, and just renders headers from the
+        # included pages
+        result = self._execute(u'^\d_,,, titlesonly')
+
+        # Hmmm: currently, Include links to the whole page for subheadings.
+        # Since this might be a bug, I pass href patterns that allow Include to
+        # change so it links to an anchor in the page.  If that is in fact the
+        # desired behaviour, those href patterns should make the anchor
+        # mandatory!
+
+        match = self._assert_link("./1_Intro", 'Intro', result)
+        match = self._assert_link("./1_Intro(#\S+)?", 'Ooga', result, pos=match.end())
+        match = self._assert_link("./1_Intro(#\S+)?", 'Argle', result, pos=match.end())
+        match = self._assert_link("./2_BlahBlah", 'Blah Blah', result, pos=match.end())
+        match = self._assert_link("./3_Conclusions", 'Conclusions', result, pos=match.end())
+
+        assert 'This is an introduction.' not in result
+        assert 'Ooga-booga.  Ding-dong.' not in result
+        assert 'Argle bargle snarky weeb.' not in result
+        assert 'more blah blah blah' not in result
+        assert 'Clearly, the snarky weeb rocks.' not in result



More information about the Moin-user mailing list