diff options
author | R. Tyler Ballance <tyler@slide.com> | 2009-07-16 15:25:04 -0700 |
---|---|---|
committer | R. Tyler Ballance <tyler@slide.com> | 2009-07-16 15:25:04 -0700 |
commit | 832a7c766de46cff23d6716ece9efd79db78cf5d (patch) | |
tree | 33e02b22a69491ea12241461ffdc9caa1f65d15b /cheetah/Tests | |
parent | dc896aa348b7d5e4dbeed440c6ae8cf8ebdf2fdd (diff) | |
download | python-cheetah-832a7c766de46cff23d6716ece9efd79db78cf5d.tar.gz |
Rename the root package to "cheetah" instead of "src" to follow more conventional python package naming
Diffstat (limited to 'cheetah/Tests')
-rw-r--r-- | cheetah/Tests/CheetahWrapper.py | 545 | ||||
-rw-r--r-- | cheetah/Tests/Filters.py | 68 | ||||
-rw-r--r-- | cheetah/Tests/NameMapper.py | 526 | ||||
-rw-r--r-- | cheetah/Tests/Regressions.py | 248 | ||||
-rw-r--r-- | cheetah/Tests/SyntaxAndOutput.py | 3227 | ||||
-rw-r--r-- | cheetah/Tests/Template.py | 353 | ||||
-rwxr-xr-x | cheetah/Tests/Test.py | 47 | ||||
-rw-r--r-- | cheetah/Tests/Unicode.py | 185 | ||||
-rw-r--r-- | cheetah/Tests/__init__.py | 1 | ||||
-rwxr-xr-x | cheetah/Tests/unittest_local_copy.py | 978 | ||||
-rw-r--r-- | cheetah/Tests/xmlrunner.py | 381 |
11 files changed, 6559 insertions, 0 deletions
diff --git a/cheetah/Tests/CheetahWrapper.py b/cheetah/Tests/CheetahWrapper.py new file mode 100644 index 0000000..ca7b0ae --- /dev/null +++ b/cheetah/Tests/CheetahWrapper.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python +''' +Tests for the 'cheetah' command. + +Besides unittest usage, recognizes the following command-line options: + --list CheetahWrapper.py + List all scenarios that are tested. The argument is the path + of this script. + --nodelete + Don't delete scratch directory at end. + --output + Show the output of each subcommand. (Normally suppressed.) +''' +import os +import popen2 +import re # Used by listTests. +import shutil +import sys +import tempfile +import unittest + +from optparse import OptionParser +from Cheetah.CheetahWrapper import CheetahWrapper # Used by NoBackup. + + +DELETE = True # True to clean up after ourselves, False for debugging. +OUTPUT = False # Normally False, True for debugging. + +BACKUP_SUFFIX = CheetahWrapper.BACKUP_SUFFIX + +def warn(msg): + sys.stderr.write(msg + '\n') + +class CFBase(unittest.TestCase): + """Base class for "cheetah compile" and "cheetah fill" unit tests. + """ + srcDir = '' # Nonblank to create source directory. + subdirs = ('child', 'child/grandkid') # Delete in reverse order. + srcFiles = ('a.tmpl', 'child/a.tmpl', 'child/grandkid/a.tmpl') + expectError = False # Used by --list option. + + def inform(self, message): + if self.verbose: + print message + + def setUp(self): + """Create the top-level directories, subdirectories and .tmpl + files. + """ + I = self.inform + # Step 1: Create the scratch directory and chdir into it. + self.scratchDir = scratchDir = tempfile.mktemp() + os.mkdir(scratchDir) + self.origCwd = os.getcwd() + os.chdir(scratchDir) + if self.srcDir: + os.mkdir(self.srcDir) + # Step 2: Create source subdirectories. + for dir in self.subdirs: + os.mkdir(dir) + # Step 3: Create the .tmpl files, each in its proper directory. + for fil in self.srcFiles: + f = open(fil, 'w') + f.write("Hello, world!\n") + f.close() + + + def tearDown(self): + os.chdir(self.origCwd) + if DELETE: + shutil.rmtree(self.scratchDir, True) # Ignore errors. + if os.path.exists(self.scratchDir): + warn("Warning: unable to delete scratch directory %s") + else: + warn("Warning: not deleting scratch directory %s" % self.scratchDir) + + + def _checkDestFileHelper(self, path, expected, + allowSurroundingText, errmsg): + """Low-level helper to check a destination file. + + in : path, string, the destination path. + expected, string, the expected contents. + allowSurroundingtext, bool, allow the result to contain + additional text around the 'expected' substring? + errmsg, string, the error message. It may contain the + following "%"-operator keys: path, expected, result. + out: None + """ + path = os.path.abspath(path) + exists = os.path.exists(path) + msg = "destination file missing: %s" % path + self.failUnless(exists, msg) + f = open(path, 'r') + result = f.read() + f.close() + if allowSurroundingText: + success = result.find(expected) != -1 + else: + success = result == expected + msg = errmsg % locals() + self.failUnless(success, msg) + + + def checkCompile(self, path): + # Raw string to prevent "\n" from being converted to a newline. + #expected = R"write('Hello, world!\n')" + expected = "Hello, world!" # might output a u'' string + errmsg = """\ +destination file %(path)s doesn't contain expected substring: +%(expected)r""" + self._checkDestFileHelper(path, expected, True, errmsg) + + + def checkFill(self, path): + expected = "Hello, world!\n" + errmsg = """\ +destination file %(path)s contains wrong result. +Expected %(expected)r +Found %(result)r""" + self._checkDestFileHelper(path, expected, False, errmsg) + + + def checkSubdirPyInit(self, path): + """Verify a destination subdirectory exists and contains an + __init__.py file. + """ + exists = os.path.exists(path) + msg = "destination subdirectory %s misssing" % path + self.failUnless(exists, msg) + initPath = os.path.join(path, "__init__.py") + exists = os.path.exists(initPath) + msg = "destination init file missing: %s" % initPath + self.failUnless(exists, msg) + + + def checkNoBackup(self, path): + """Verify 'path' does not exist. (To check --nobackup.) + """ + exists = os.path.exists(path) + msg = "backup file exists in spite of --nobackup: %s" % path + self.failIf(exists, msg) + + + def assertWin32Subprocess(self, cmd): + _in, _out = os.popen4(cmd) + _in.close() + output = _out.read() + rc = _out.close() + if rc is None: + rc = 0 + return rc, output + + def assertPosixSubprocess(self, cmd): + process = popen2.Popen4(cmd) + process.tochild.close() + output = process.fromchild.read() + status = process.wait() + process.fromchild.close() + return status, output + + def assertSubprocess(self, cmd, nonzero=False): + status, output = None, None + if sys.platform == 'win32': + status, output = self.assertWin32Subprocess(cmd) + else: + status, output = self.assertPosixSubprocess(cmd) + + if not nonzero: + self.failUnlessEqual(status, 0, '''Subprocess exited with a non-zero status (%d) + %s''' % (status, output)) + else: + self.failIfEqual(status, 0, '''Subprocess exited with a zero status (%d) + %s''' % (status, output)) + return output + + def go(self, cmd, expectedStatus=0, expectedOutputSubstring=None): + """Run a "cheetah compile" or "cheetah fill" subcommand. + + in : cmd, string, the command to run. + expectedStatus, int, subcommand's expected output status. + 0 if the subcommand is expected to succeed, 1-255 otherwise. + expectedOutputSubstring, string, substring which much appear + in the standard output or standard error. None to skip this + test. + out: None. + """ + output = self.assertSubprocess(cmd) + if expectedOutputSubstring is not None: + msg = "substring %r not found in subcommand output: %s" % \ + (expectedOutputSubstring, cmd) + substringTest = output.find(expectedOutputSubstring) != -1 + self.failUnless(substringTest, msg) + + +class CFIdirBase(CFBase): + """Subclass for tests with --idir. + """ + srcDir = 'SRC' + subdirs = ('SRC/child', 'SRC/child/grandkid') # Delete in reverse order. + srcFiles = ('SRC/a.tmpl', 'SRC/child/a.tmpl', 'SRC/child/grandkid/a.tmpl') + + + +################################################## +## TEST CASE CLASSES + +class OneFile(CFBase): + def testCompile(self): + self.go("cheetah compile a.tmpl") + self.checkCompile("a.py") + + def testFill(self): + self.go("cheetah fill a.tmpl") + self.checkFill("a.html") + + def testText(self): + self.go("cheetah fill --oext txt a.tmpl") + self.checkFill("a.txt") + + +class OneFileNoExtension(CFBase): + def testCompile(self): + self.go("cheetah compile a") + self.checkCompile("a.py") + + def testFill(self): + self.go("cheetah fill a") + self.checkFill("a.html") + + def testText(self): + self.go("cheetah fill --oext txt a") + self.checkFill("a.txt") + + +class SplatTmpl(CFBase): + def testCompile(self): + self.go("cheetah compile *.tmpl") + self.checkCompile("a.py") + + def testFill(self): + self.go("cheetah fill *.tmpl") + self.checkFill("a.html") + + def testText(self): + self.go("cheetah fill --oext txt *.tmpl") + self.checkFill("a.txt") + +class ThreeFilesWithSubdirectories(CFBase): + def testCompile(self): + self.go("cheetah compile a.tmpl child/a.tmpl child/grandkid/a.tmpl") + self.checkCompile("a.py") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill a.tmpl child/a.tmpl child/grandkid/a.tmpl") + self.checkFill("a.html") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill --oext txt a.tmpl child/a.tmpl child/grandkid/a.tmpl") + self.checkFill("a.txt") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class ThreeFilesWithSubdirectoriesNoExtension(CFBase): + def testCompile(self): + self.go("cheetah compile a child/a child/grandkid/a") + self.checkCompile("a.py") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill a child/a child/grandkid/a") + self.checkFill("a.html") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill --oext txt a child/a child/grandkid/a") + self.checkFill("a.txt") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class SplatTmplWithSubdirectories(CFBase): + def testCompile(self): + self.go("cheetah compile *.tmpl child/*.tmpl child/grandkid/*.tmpl") + self.checkCompile("a.py") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill *.tmpl child/*.tmpl child/grandkid/*.tmpl") + self.checkFill("a.html") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill --oext txt *.tmpl child/*.tmpl child/grandkid/*.tmpl") + self.checkFill("a.txt") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class OneFileWithOdir(CFBase): + def testCompile(self): + self.go("cheetah compile --odir DEST a.tmpl") + self.checkSubdirPyInit("DEST") + self.checkCompile("DEST/a.py") + + def testFill(self): + self.go("cheetah fill --odir DEST a.tmpl") + self.checkFill("DEST/a.html") + + def testText(self): + self.go("cheetah fill --odir DEST --oext txt a.tmpl") + self.checkFill("DEST/a.txt") + + +class VarietyWithOdir(CFBase): + def testCompile(self): + self.go("cheetah compile --odir DEST a.tmpl child/a child/grandkid/*.tmpl") + self.checkSubdirPyInit("DEST") + self.checkSubdirPyInit("DEST/child") + self.checkSubdirPyInit("DEST/child/grandkid") + self.checkCompile("DEST/a.py") + self.checkCompile("DEST/child/a.py") + self.checkCompile("DEST/child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill --odir DEST a.tmpl child/a child/grandkid/*.tmpl") + self.checkFill("DEST/a.html") + self.checkFill("DEST/child/a.html") + self.checkFill("DEST/child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill --odir DEST --oext txt a.tmpl child/a child/grandkid/*.tmpl") + self.checkFill("DEST/a.txt") + self.checkFill("DEST/child/a.txt") + self.checkFill("DEST/child/grandkid/a.txt") + + +class RecurseExplicit(CFBase): + def testCompile(self): + self.go("cheetah compile -R child") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill -R child") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill -R --oext txt child") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class RecurseImplicit(CFBase): + def testCompile(self): + self.go("cheetah compile -R") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill -R") + self.checkFill("a.html") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill -R --oext txt") + self.checkFill("a.txt") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class RecurseExplicitWIthOdir(CFBase): + def testCompile(self): + self.go("cheetah compile -R --odir DEST child") + self.checkSubdirPyInit("DEST/child") + self.checkSubdirPyInit("DEST/child/grandkid") + self.checkCompile("DEST/child/a.py") + self.checkCompile("DEST/child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill -R --odir DEST child") + self.checkFill("DEST/child/a.html") + self.checkFill("DEST/child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill -R --odir DEST --oext txt child") + self.checkFill("DEST/child/a.txt") + self.checkFill("DEST/child/grandkid/a.txt") + + +class Flat(CFBase): + def testCompile(self): + self.go("cheetah compile --flat child/a.tmpl") + self.checkCompile("a.py") + + def testFill(self): + self.go("cheetah fill --flat child/a.tmpl") + self.checkFill("a.html") + + def testText(self): + self.go("cheetah fill --flat --oext txt child/a.tmpl") + self.checkFill("a.txt") + + +class FlatRecurseCollision(CFBase): + expectError = True + + def testCompile(self): + self.assertSubprocess("cheetah compile -R --flat", nonzero=True) + + def testFill(self): + self.assertSubprocess("cheetah fill -R --flat", nonzero=True) + + def testText(self): + self.assertSubprocess("cheetah fill -R --flat", nonzero=True) + + +class IdirRecurse(CFIdirBase): + def testCompile(self): + self.go("cheetah compile -R --idir SRC child") + self.checkSubdirPyInit("child") + self.checkSubdirPyInit("child/grandkid") + self.checkCompile("child/a.py") + self.checkCompile("child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill -R --idir SRC child") + self.checkFill("child/a.html") + self.checkFill("child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill -R --idir SRC --oext txt child") + self.checkFill("child/a.txt") + self.checkFill("child/grandkid/a.txt") + + +class IdirOdirRecurse(CFIdirBase): + def testCompile(self): + self.go("cheetah compile -R --idir SRC --odir DEST child") + self.checkSubdirPyInit("DEST/child") + self.checkSubdirPyInit("DEST/child/grandkid") + self.checkCompile("DEST/child/a.py") + self.checkCompile("DEST/child/grandkid/a.py") + + def testFill(self): + self.go("cheetah fill -R --idir SRC --odir DEST child") + self.checkFill("DEST/child/a.html") + self.checkFill("DEST/child/grandkid/a.html") + + def testText(self): + self.go("cheetah fill -R --idir SRC --odir DEST --oext txt child") + self.checkFill("DEST/child/a.txt") + self.checkFill("DEST/child/grandkid/a.txt") + + +class IdirFlatRecurseCollision(CFIdirBase): + expectError = True + + def testCompile(self): + self.assertSubprocess("cheetah compile -R --flat --idir SRC", nonzero=True) + + def testFill(self): + self.assertSubprocess("cheetah fill -R --flat --idir SRC", nonzero=True) + + def testText(self): + self.assertSubprocess("cheetah fill -R --flat --idir SRC --oext txt", nonzero=True) + + +class NoBackup(CFBase): + """Run the command twice each time and verify a backup file is + *not* created. + """ + def testCompile(self): + self.go("cheetah compile --nobackup a.tmpl") + self.go("cheetah compile --nobackup a.tmpl") + self.checkNoBackup("a.py" + BACKUP_SUFFIX) + + def testFill(self): + self.go("cheetah fill --nobackup a.tmpl") + self.go("cheetah fill --nobackup a.tmpl") + self.checkNoBackup("a.html" + BACKUP_SUFFIX) + + def testText(self): + self.go("cheetah fill --nobackup --oext txt a.tmpl") + self.go("cheetah fill --nobackup --oext txt a.tmpl") + self.checkNoBackup("a.txt" + BACKUP_SUFFIX) + +def listTests(cheetahWrapperFile): + """cheetahWrapperFile, string, path of this script. + + XXX TODO: don't print test where expectError is true. + """ + rx = re.compile( R'self\.go\("(.*?)"\)' ) + f = open(cheetahWrapperFile) + while 1: + lin = f.readline() + if not lin: + break + m = rx.search(lin) + if m: + print m.group(1) + f.close() + +def main(): + global DELETE, OUTPUT + parser = OptionParser() + parser.add_option("--list", action="store", dest="listTests") + parser.add_option("--nodelete", action="store_true") + parser.add_option("--output", action="store_true") + # The following options are passed to unittest. + parser.add_option("-e", "--explain", action="store_true") + parser.add_option("-v", "--verbose", action="store_true") + parser.add_option("-q", "--quiet", action="store_true") + opts, files = parser.parse_args() + if opts.nodelete: + DELETE = False + if opts.output: + OUTPUT = True + if opts.listTests: + listTests(opts.listTests) + else: + # Eliminate script-specific command-line arguments to prevent + # errors in unittest. + del sys.argv[1:] + for opt in ("explain", "verbose", "quiet"): + if getattr(opts, opt): + sys.argv.append("--" + opt) + sys.argv.extend(files) + unittest.main() + +if __name__ == '__main__': + main() + +# vim: sw=4 ts=4 expandtab diff --git a/cheetah/Tests/Filters.py b/cheetah/Tests/Filters.py new file mode 100644 index 0000000..bf35440 --- /dev/null +++ b/cheetah/Tests/Filters.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import sys + +import Cheetah.Template +import Cheetah.Filters + +import unittest_local_copy as unittest + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +class BasicMarkdownFilterTest(unittest.TestCase): + ''' + Test that our markdown filter works + ''' + def test_BasicHeader(self): + template = ''' +#from Cheetah.Filters import Markdown +#transform Markdown +$foo + +Header +====== + ''' + expected = '''<p>bar</p> +<h1>Header</h1>''' + try: + template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}]) + template = str(template) + assert template == expected + except Exception, ex: + if ex.__class__.__name__ == 'MarkdownException' and majorVer == 2 and minorVer < 5: + print '>>> NOTE: Support for the Markdown filter will be broken for you. Markdown says: %s' % ex + return + raise + + +class BasicCodeHighlighterFilterTest(unittest.TestCase): + ''' + Test that our code highlighter filter works + ''' + def test_Python(self): + template = ''' +#from Cheetah.Filters import CodeHighlighter +#transform CodeHighlighter + +def foo(self): + return '$foo' + ''' + template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}]) + template = str(template) + assert template, (template, 'We should have some content here...') + + def test_Html(self): + template = ''' +#from Cheetah.Filters import CodeHighlighter +#transform CodeHighlighter + +<html><head></head><body>$foo</body></html> + ''' + template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}]) + template = str(template) + assert template, (template, 'We should have some content here...') + + +if __name__ == '__main__': + unittest.main() diff --git a/cheetah/Tests/NameMapper.py b/cheetah/Tests/NameMapper.py new file mode 100644 index 0000000..5d6b8b6 --- /dev/null +++ b/cheetah/Tests/NameMapper.py @@ -0,0 +1,526 @@ +#!/usr/bin/env python + +from __future__ import generators +import sys +import types +import os +import os.path + +#import unittest_local_copy as unittest +import unittest +from Cheetah.NameMapper import NotFound, valueForKey, \ + valueForName, valueFromSearchList, valueFromFrame, valueFromFrameOrSearchList + + +class DummyClass: + classVar1 = 123 + + def __init__(self): + self.instanceVar1 = 123 + + def __str__(self): + return 'object' + + def meth(self, arg="arff"): + return str(arg) + + def meth1(self, arg="doo"): + return arg + + def meth2(self, arg1="a1", arg2="a2"): + raise ValueError + + def meth3(self): + """Tests a bug that Jeff Johnson reported on Oct 1, 2001""" + + x = 'A string' + try: + for i in [1,2,3,4]: + if x == 2: + pass + + if x == 'xx': + pass + return x + except: + raise + + +def dummyFunc(arg="Scooby"): + return arg + +def funcThatRaises(): + raise ValueError + + +testNamespace = { + 'aStr':'blarg', + 'anInt':1, + 'aFloat':1.5, + 'aDict': {'one':'item1', + 'two':'item2', + 'nestedDict':{'one':'nestedItem1', + 'two':'nestedItem2', + 'funcThatRaises':funcThatRaises, + 'aClass': DummyClass, + }, + 'nestedFunc':dummyFunc, + }, + 'aClass': DummyClass, + 'aFunc': dummyFunc, + 'anObj': DummyClass(), + 'aMeth': DummyClass().meth1, + 'none' : None, + 'emptyString':'', + 'funcThatRaises':funcThatRaises, + } + +autoCallResults = {'aFunc':'Scooby', + 'aMeth':'doo', + } + +results = testNamespace.copy() +results.update({'anObj.meth1':'doo', + 'aDict.one':'item1', + 'aDict.nestedDict':testNamespace['aDict']['nestedDict'], + 'aDict.nestedDict.one':'nestedItem1', + 'aDict.nestedDict.aClass':DummyClass, + 'aDict.nestedFunc':'Scooby', + 'aClass.classVar1':123, + 'anObj.instanceVar1':123, + 'anObj.meth3':'A string', + }) + +for k in testNamespace.keys(): + # put them in the globals for the valueFromFrame tests + exec '%s = testNamespace[k]'%k + +################################################## +## TEST BASE CLASSES + +class NameMapperTest(unittest.TestCase): + failureException = (NotFound,AssertionError) + _testNamespace = testNamespace + _results = results + + def namespace(self): + return self._testNamespace + + def VFN(self, name, autocall=True): + return valueForName(self.namespace(), name, autocall) + + def VFS(self, searchList, name, autocall=True): + return valueFromSearchList(searchList, name, autocall) + + + # alias to be overriden later + get = VFN + + def check(self, name): + got = self.get(name) + if autoCallResults.has_key(name): + expected = autoCallResults[name] + else: + expected = self._results[name] + assert got == expected + + +################################################## +## TEST CASE CLASSES + +class VFN(NameMapperTest): + + def test1(self): + """string in dict lookup""" + self.check('aStr') + + def test2(self): + """string in dict lookup in a loop""" + for i in range(10): + self.check('aStr') + + def test3(self): + """int in dict lookup""" + self.check('anInt') + + def test4(self): + """int in dict lookup in a loop""" + for i in range(10): + self.check('anInt') + + def test5(self): + """float in dict lookup""" + self.check('aFloat') + + def test6(self): + """float in dict lookup in a loop""" + for i in range(10): + self.check('aFloat') + + def test7(self): + """class in dict lookup""" + self.check('aClass') + + def test8(self): + """class in dict lookup in a loop""" + for i in range(10): + self.check('aClass') + + def test9(self): + """aFunc in dict lookup""" + self.check('aFunc') + + def test10(self): + """aFunc in dict lookup in a loop""" + for i in range(10): + self.check('aFunc') + + def test11(self): + """aMeth in dict lookup""" + self.check('aMeth') + + def test12(self): + """aMeth in dict lookup in a loop""" + for i in range(10): + self.check('aMeth') + + def test13(self): + """aMeth in dict lookup""" + self.check('aMeth') + + def test14(self): + """aMeth in dict lookup in a loop""" + for i in range(10): + self.check('aMeth') + + def test15(self): + """anObj in dict lookup""" + self.check('anObj') + + def test16(self): + """anObj in dict lookup in a loop""" + for i in range(10): + self.check('anObj') + + def test17(self): + """aDict in dict lookup""" + self.check('aDict') + + def test18(self): + """aDict in dict lookup in a loop""" + for i in range(10): + self.check('aDict') + + def test17(self): + """aDict in dict lookup""" + self.check('aDict') + + def test18(self): + """aDict in dict lookup in a loop""" + for i in range(10): + self.check('aDict') + + def test19(self): + """aClass.classVar1 in dict lookup""" + self.check('aClass.classVar1') + + def test20(self): + """aClass.classVar1 in dict lookup in a loop""" + for i in range(10): + self.check('aClass.classVar1') + + + def test23(self): + """anObj.instanceVar1 in dict lookup""" + self.check('anObj.instanceVar1') + + def test24(self): + """anObj.instanceVar1 in dict lookup in a loop""" + for i in range(10): + self.check('anObj.instanceVar1') + + ## tests 22, 25, and 26 removed when the underscored lookup was removed + + def test27(self): + """anObj.meth1 in dict lookup""" + self.check('anObj.meth1') + + def test28(self): + """anObj.meth1 in dict lookup in a loop""" + for i in range(10): + self.check('anObj.meth1') + + def test29(self): + """aDict.one in dict lookup""" + self.check('aDict.one') + + def test30(self): + """aDict.one in dict lookup in a loop""" + for i in range(10): + self.check('aDict.one') + + def test31(self): + """aDict.nestedDict in dict lookup""" + self.check('aDict.nestedDict') + + def test32(self): + """aDict.nestedDict in dict lookup in a loop""" + for i in range(10): + self.check('aDict.nestedDict') + + def test33(self): + """aDict.nestedDict.one in dict lookup""" + self.check('aDict.nestedDict.one') + + def test34(self): + """aDict.nestedDict.one in dict lookup in a loop""" + for i in range(10): + self.check('aDict.nestedDict.one') + + def test35(self): + """aDict.nestedFunc in dict lookup""" + self.check('aDict.nestedFunc') + + def test36(self): + """aDict.nestedFunc in dict lookup in a loop""" + for i in range(10): + self.check('aDict.nestedFunc') + + def test37(self): + """aDict.nestedFunc in dict lookup - without autocalling""" + assert self.get('aDict.nestedFunc', False) == dummyFunc + + def test38(self): + """aDict.nestedFunc in dict lookup in a loop - without autocalling""" + for i in range(10): + assert self.get('aDict.nestedFunc', False) == dummyFunc + + def test39(self): + """aMeth in dict lookup - without autocalling""" + assert self.get('aMeth', False) == self.namespace()['aMeth'] + + def test40(self): + """aMeth in dict lookup in a loop - without autocalling""" + for i in range(10): + assert self.get('aMeth', False) == self.namespace()['aMeth'] + + def test41(self): + """anObj.meth3 in dict lookup""" + self.check('anObj.meth3') + + def test42(self): + """aMeth in dict lookup in a loop""" + for i in range(10): + self.check('anObj.meth3') + + def test43(self): + """NotFound test""" + + def test(self=self): + self.get('anObj.methX') + self.assertRaises(NotFound,test) + + def test44(self): + """NotFound test in a loop""" + def test(self=self): + self.get('anObj.methX') + + for i in range(10): + self.assertRaises(NotFound,test) + + def test45(self): + """Other exception from meth test""" + + def test(self=self): + self.get('anObj.meth2') + self.assertRaises(ValueError, test) + + def test46(self): + """Other exception from meth test in a loop""" + def test(self=self): + self.get('anObj.meth2') + + for i in range(10): + self.assertRaises(ValueError,test) + + def test47(self): + """None in dict lookup""" + self.check('none') + + def test48(self): + """None in dict lookup in a loop""" + for i in range(10): + self.check('none') + + def test49(self): + """EmptyString in dict lookup""" + self.check('emptyString') + + def test50(self): + """EmptyString in dict lookup in a loop""" + for i in range(10): + self.check('emptyString') + + def test51(self): + """Other exception from func test""" + + def test(self=self): + self.get('funcThatRaises') + self.assertRaises(ValueError, test) + + def test52(self): + """Other exception from func test in a loop""" + def test(self=self): + self.get('funcThatRaises') + + for i in range(10): + self.assertRaises(ValueError,test) + + + def test53(self): + """Other exception from func test""" + + def test(self=self): + self.get('aDict.nestedDict.funcThatRaises') + self.assertRaises(ValueError, test) + + def test54(self): + """Other exception from func test in a loop""" + def test(self=self): + self.get('aDict.nestedDict.funcThatRaises') + + for i in range(10): + self.assertRaises(ValueError,test) + + def test55(self): + """aDict.nestedDict.aClass in dict lookup""" + self.check('aDict.nestedDict.aClass') + + def test56(self): + """aDict.nestedDict.aClass in dict lookup in a loop""" + for i in range(10): + self.check('aDict.nestedDict.aClass') + + def test57(self): + """aDict.nestedDict.aClass in dict lookup - without autocalling""" + assert self.get('aDict.nestedDict.aClass', False) == DummyClass + + def test58(self): + """aDict.nestedDict.aClass in dict lookup in a loop - without autocalling""" + for i in range(10): + assert self.get('aDict.nestedDict.aClass', False) == DummyClass + + def test59(self): + """Other exception from func test -- but without autocalling shouldn't raise""" + + self.get('aDict.nestedDict.funcThatRaises', False) + + def test60(self): + """Other exception from func test in a loop -- but without autocalling shouldn't raise""" + + for i in range(10): + self.get('aDict.nestedDict.funcThatRaises', False) + +class VFS(VFN): + _searchListLength = 1 + + def searchList(self): + lng = self._searchListLength + if lng == 1: + return [self.namespace()] + elif lng == 2: + return [self.namespace(),{'dummy':1234}] + elif lng == 3: + # a tuple for kicks + return ({'dummy':1234}, self.namespace(),{'dummy':1234}) + elif lng == 4: + # a generator for more kicks + return self.searchListGenerator() + + def searchListGenerator(self): + class Test: + pass + for i in [Test(),{'dummy':1234}, self.namespace(),{'dummy':1234}]: + yield i + + def get(self, name, autocall=True): + return self.VFS(self.searchList(), name, autocall) + +class VFS_2namespaces(VFS): + _searchListLength = 2 + +class VFS_3namespaces(VFS): + _searchListLength = 3 + +class VFS_4namespaces(VFS): + _searchListLength = 4 + +class VFF(VFN): + def get(self, name, autocall=True): + ns = self._testNamespace + aStr = ns['aStr'] + aFloat = ns['aFloat'] + none = 'some' + return valueFromFrame(name, autocall) + + def setUp(self): + """Mod some of the data + """ + self._testNamespace = ns = self._testNamespace.copy() + self._results = res = self._results.copy() + ns['aStr'] = res['aStr'] = 'BLARG' + ns['aFloat'] = res['aFloat'] = 0.1234 + res['none'] = 'some' + res['True'] = True + res['False'] = False + res['None'] = None + res['eval'] = eval + + def test_VFF_1(self): + """Builtins""" + self.check('True') + self.check('None') + self.check('False') + assert self.get('eval', False)==eval + assert self.get('range', False)==range + +class VFFSL(VFS): + _searchListLength = 1 + + def setUp(self): + """Mod some of the data + """ + self._testNamespace = ns = self._testNamespace.copy() + self._results = res = self._results.copy() + ns['aStr'] = res['aStr'] = 'BLARG' + ns['aFloat'] = res['aFloat'] = 0.1234 + res['none'] = 'some' + + del ns['anInt'] # will be picked up by globals + + def VFFSL(self, searchList, name, autocall=True): + anInt = 1 + none = 'some' + return valueFromFrameOrSearchList(searchList, name, autocall) + + def get(self, name, autocall=True): + return self.VFFSL(self.searchList(), name, autocall) + +class VFFSL_2(VFFSL): + _searchListLength = 2 + +class VFFSL_3(VFFSL): + _searchListLength = 3 + +class VFFSL_4(VFFSL): + _searchListLength = 4 + +if sys.platform.startswith('java'): + del VFF, VFFSL, VFFSL_2, VFFSL_3, VFFSL_4 + + +################################################## +## if run from the command line ## + +if __name__ == '__main__': + unittest.main() diff --git a/cheetah/Tests/Regressions.py b/cheetah/Tests/Regressions.py new file mode 100644 index 0000000..b5ba3d0 --- /dev/null +++ b/cheetah/Tests/Regressions.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +import Cheetah.NameMapper +import Cheetah.Template + +import pdb +import sys + +import unittest_local_copy as unittest # This is just stupid + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +def isPython23(): + ''' Python 2.3 is still supported by Cheetah, but doesn't support decorators ''' + return majorVer == 2 and minorVer < 4 + +class GetAttrException(Exception): + pass + +class CustomGetAttrClass(object): + def __getattr__(self, name): + raise GetAttrException('FAIL, %s' % name) + +class GetAttrTest(unittest.TestCase): + ''' + Test for an issue occurring when __getatttr__() raises an exception + causing NameMapper to raise a NotFound exception + ''' + def test_ValidException(self): + o = CustomGetAttrClass() + try: + print o.attr + except GetAttrException, e: + # expected + return + except: + self.fail('Invalid exception raised: %s' % e) + self.fail('Should have had an exception raised') + + def test_NotFoundException(self): + template = ''' + #def raiseme() + $obj.attr + #end def''' + + template = Cheetah.Template.Template.compile(template, compilerSettings={}, keepRefToGeneratedCode=True) + template = template(searchList=[{'obj' : CustomGetAttrClass()}]) + assert template, 'We should have a valid template object by now' + + self.failUnlessRaises(GetAttrException, template.raiseme) + + +class InlineImportTest(unittest.TestCase): + def test_FromFooImportThing(self): + ''' + Verify that a bug introduced in v2.1.0 where an inline: + #from module import class + would result in the following code being generated: + import class + ''' + template = ''' + #def myfunction() + #if True + #from os import path + #return 17 + Hello! + #end if + #end def + ''' + template = Cheetah.Template.Template.compile(template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True) + template = template(searchList=[{}]) + + assert template, 'We should have a valid template object by now' + + rc = template.myfunction() + assert rc == 17, (template, 'Didn\'t get a proper return value') + + def test_ImportFailModule(self): + template = ''' + #try + #import invalidmodule + #except + #set invalidmodule = dict(FOO='BAR!') + #end try + + $invalidmodule.FOO + ''' + template = Cheetah.Template.Template.compile(template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True) + template = template(searchList=[{}]) + + assert template, 'We should have a valid template object by now' + assert str(template), 'We weren\'t able to properly generate the result from the template' + + def test_ProperImportOfBadModule(self): + template = ''' + #from invalid import fail + + This should totally $fail + ''' + self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True) + + def test_AutoImporting(self): + template = ''' + #extends FakeyTemplate + + Boo! + ''' + self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template) + + def test_StuffBeforeImport_Legacy(self): + template = ''' +### +### I like comments before import +### +#extends Foo +Bar +''' + self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template, compilerSettings={'useLegacyImportMode' : True}, keepRefToGeneratedCode=True) + + +class Mantis_Issue_11_Regression_Test(unittest.TestCase): + ''' + Test case for bug outlined in Mantis issue #11: + + Output: + Traceback (most recent call last): + File "test.py", line 12, in <module> + t.respond() + File "DynamicallyCompiledCheetahTemplate.py", line 86, in respond + File "/usr/lib64/python2.6/cgi.py", line 1035, in escape + s = s.replace("&", "&") # Must be done first! + ''' + def test_FailingBehavior(self): + import cgi + template = Cheetah.Template.Template("$escape($request)", searchList=[{'escape' : cgi.escape, 'request' : 'foobar'}]) + assert template + self.failUnlessRaises(AttributeError, template.respond) + + + def test_FailingBehaviorWithSetting(self): + import cgi + template = Cheetah.Template.Template("$escape($request)", + searchList=[{'escape' : cgi.escape, 'request' : 'foobar'}], + compilerSettings={'prioritizeSearchListOverSelf' : True}) + assert template + assert template.respond() + +class Mantis_Issue_21_Regression_Test(unittest.TestCase): + ''' + Test case for bug outlined in issue #21 + + Effectively @staticmethod and @classmethod + decorated methods in templates don't + properly define the _filter local, which breaks + when using the NameMapper + ''' + def runTest(self): + if isPython23(): + return + template = ''' + #@staticmethod + #def testMethod() + This is my $output + #end def + ''' + template = Cheetah.Template.Template.compile(template) + assert template + assert template.testMethod(output='bug') # raises a NameError: global name '_filter' is not defined + + +class Mantis_Issue_22_Regression_Test(unittest.TestCase): + ''' + Test case for bug outlined in issue #22 + + When using @staticmethod and @classmethod + in conjunction with the #filter directive + the generated code for the #filter is reliant + on the `self` local, breaking the function + ''' + def test_NoneFilter(self): + # XXX: Disabling this test for now + return + if isPython23(): + return + template = ''' + #@staticmethod + #def testMethod() + #filter None + This is my $output + #end filter + #end def + ''' + template = Cheetah.Template.Template.compile(template) + assert template + assert template.testMethod(output='bug') + + def test_DefinedFilter(self): + # XXX: Disabling this test for now + return + if isPython23(): + return + template = ''' + #@staticmethod + #def testMethod() + #filter Filter + This is my $output + #end filter + #end def + ''' + # The generated code for the template's testMethod() should look something + # like this in the 'error' case: + ''' + @staticmethod + def testMethod(**KWS): + ## CHEETAH: generated from #def testMethod() at line 3, col 13. + trans = DummyTransaction() + _dummyTrans = True + write = trans.response().write + SL = [KWS] + _filter = lambda x, **kwargs: unicode(x) + + ######################################## + ## START - generated method body + + _orig_filter_18517345 = _filter + filterName = u'Filter' + if self._CHEETAH__filters.has_key("Filter"): + _filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName] + else: + _filter = self._CHEETAH__currentFilter = \ + self._CHEETAH__filters[filterName] = getattr(self._CHEETAH__filtersLib, filterName)(self).filter + write(u' This is my ') + _v = VFFSL(SL,"output",True) # u'$output' on line 5, col 32 + if _v is not None: write(_filter(_v, rawExpr=u'$output')) # from line 5, col 32. + + ######################################## + ## END - generated method body + + return _dummyTrans and trans.response().getvalue() or "" + ''' + template = Cheetah.Template.Template.compile(template) + assert template + assert template.testMethod(output='bug') + + +if __name__ == '__main__': + unittest.main() diff --git a/cheetah/Tests/SyntaxAndOutput.py b/cheetah/Tests/SyntaxAndOutput.py new file mode 100644 index 0000000..78e0cc5 --- /dev/null +++ b/cheetah/Tests/SyntaxAndOutput.py @@ -0,0 +1,3227 @@ +#!/usr/bin/env python +# -*- coding: latin-1 -*- + +''' +Syntax and Output tests. + +TODO +- #finally +- #filter +- #errorCatcher +- #echo +- #silent +''' + + +################################################## +## DEPENDENCIES ## + +import sys +import types +import re +from copy import deepcopy +import os +import os.path +import new +import pdb +import warnings + +from Cheetah.NameMapper import NotFound +from Cheetah.NameMapper import C_VERSION as NameMapper_C_VERSION +from Cheetah.Template import Template +from Cheetah.Parser import ParseError +from Cheetah.Compiler import Compiler, DEFAULT_COMPILER_SETTINGS +import unittest_local_copy as unittest + +class Unspecified(object): + pass + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +def testdecorator(func): + return func + +class DummyClass: + _called = False + def __str__(self): + return 'object' + + def meth(self, arg="arff"): + return str(arg) + + def meth1(self, arg="doo"): + return arg + + def meth2(self, arg1="a1", arg2="a2"): + return str(arg1) + str(arg2) + + def methWithPercentSignDefaultArg(self, arg1="110%"): + return str(arg1) + + def callIt(self, arg=1234): + self._called = True + self._callArg = arg + + +def dummyFunc(arg="Scooby"): + return arg + +defaultTestNameSpace = { + 'aStr':'blarg', + 'anInt':1, + 'aFloat':1.5, + 'aList': ['item0','item1','item2'], + 'aDict': {'one':'item1', + 'two':'item2', + 'nestedDict':{1:'nestedItem1', + 'two':'nestedItem2' + }, + 'nestedFunc':dummyFunc, + }, + 'aFunc': dummyFunc, + 'anObj': DummyClass(), + 'aMeth': DummyClass().meth1, + 'aStrToBeIncluded': "$aStr $anInt", + 'none' : None, + 'emptyString':'', + 'numOne':1, + 'numTwo':2, + 'zero':0, + 'tenDigits': 1234567890, + 'webSafeTest': 'abc <=> &', + 'strip1': ' \t strippable whitespace \t\t \n', + 'strip2': ' \t strippable whitespace \t\t ', + 'strip3': ' \t strippable whitespace \t\t\n1 2 3\n', + + 'blockToBeParsed':"""$numOne $numTwo""", + 'includeBlock2':"""$numOne $numTwo $aSetVar""", + + 'includeFileName':'parseTest.txt', + 'listOfLambdas':[lambda x: x, lambda x: x, lambda x: x,], + 'list': [ + {'index': 0, 'numOne': 1, 'numTwo': 2}, + {'index': 1, 'numOne': 1, 'numTwo': 2}, + ], + 'nameList': [('john', 'doe'), ('jane', 'smith')], + 'letterList': ['a', 'b', 'c'], + '_': lambda x: 'Translated: ' + x, + 'unicodeData':u'aoeu12345\u1234', + } + + +################################################## +## TEST BASE CLASSES + +class OutputTest(unittest.TestCase): + report = ''' +Template output mismatch: + + Input Template = +%(template)s%(end)s + + Expected Output = +%(expected)s%(end)s + + Actual Output = +%(actual)s%(end)s''' + + convertEOLs = True + _EOLreplacement = None + _debugEOLReplacement = False + + DEBUGLEV = 0 + _searchList = [defaultTestNameSpace] + + _useNewStyleCompilation = True + #_useNewStyleCompilation = False + + _extraCompileKwArgs = None + + def searchList(self): + return self._searchList + + def verify(self, input, expectedOutput, + inputEncoding=None, + outputEncoding=None, + convertEOLs=Unspecified): + if self._EOLreplacement: + if convertEOLs is Unspecified: + convertEOLs = self.convertEOLs + if convertEOLs: + input = input.replace('\n', self._EOLreplacement) + expectedOutput = expectedOutput.replace('\n', self._EOLreplacement) + + self._input = input + if self._useNewStyleCompilation: + extraKwArgs = self._extraCompileKwArgs or {} + + templateClass = Template.compile( + source=input, + compilerSettings=self._getCompilerSettings(), + keepRefToGeneratedCode=True, + **extraKwArgs + ) + moduleCode = templateClass._CHEETAH_generatedModuleCode + self.template = templateObj = templateClass(searchList=self.searchList()) + else: + self.template = templateObj = Template( + input, + searchList=self.searchList(), + compilerSettings=self._getCompilerSettings(), + ) + moduleCode = templateObj._CHEETAH_generatedModuleCode + if self.DEBUGLEV >= 1: + print moduleCode + try: + output = templateObj.respond() # rather than __str__, because of unicode + assert output==expectedOutput, self._outputMismatchReport(output, expectedOutput) + finally: + templateObj.shutdown() + + def _getCompilerSettings(self): + return {} + + def _outputMismatchReport(self, output, expectedOutput): + if self._debugEOLReplacement and self._EOLreplacement: + EOLrepl = self._EOLreplacement + marker = '*EOL*' + return self.report % {'template': self._input.replace(EOLrepl,marker), + 'expected': expectedOutput.replace(EOLrepl,marker), + 'actual': output.replace(EOLrepl,marker), + 'end': '(end)'} + else: + return self.report % {'template': self._input, + 'expected': expectedOutput, + 'actual': output, + 'end': '(end)'} + + def genClassCode(self): + if hasattr(self, 'template'): + return self.template.generatedClassCode() + + def genModuleCode(self): + if hasattr(self, 'template'): + return self.template.generatedModuleCode() + +################################################## +## TEST CASE CLASSES + +class EmptyTemplate(OutputTest): + convertEOLs = False + def test1(self): + """an empty string for the template""" + + warnings.filterwarnings('error', + 'You supplied an empty string for the source!', + UserWarning) + try: + self.verify("", "") + except UserWarning: + pass + else: + self.fail("Should warn about empty source strings.") + + try: + self.verify("#implements foo", "") + except NotImplementedError: + pass + else: + self.fail("This should barf about respond() not being implemented.") + + self.verify("#implements respond", "") + + self.verify("#implements respond(foo=1234)", "") + + +class Backslashes(OutputTest): + convertEOLs = False + + def setUp(self): + fp = open('backslashes.txt','w') + fp.write(r'\ #LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n') + fp.flush() + fp.close + + def tearDown(self): + if os.path.exists('backslashes.txt'): + os.remove('backslashes.txt') + + def test1(self): + """ a single \\ using rawstrings""" + self.verify(r"\ ", + r"\ ") + + def test2(self): + """ a single \\ using rawstrings and lots of lines""" + self.verify(r"\ " + "\n\n\n\n\n\n\n\n\n", + r"\ " + "\n\n\n\n\n\n\n\n\n") + + def test3(self): + """ a single \\ without using rawstrings""" + self.verify("\ \ ", + "\ \ ") + + def test4(self): + """ single line from an apache conf file""" + self.verify(r'#LogFormat "%h %l %u %t \"%r\" %>s %b"', + r'#LogFormat "%h %l %u %t \"%r\" %>s %b"') + + def test5(self): + """ single line from an apache conf file with many NEWLINES + + The NEWLINES are used to make sure that MethodCompiler.commitStrConst() + is handling long and short strings in the same fashion. It uses + triple-quotes for strings with lots of \\n in them and repr(theStr) for + shorter strings with only a few newlines.""" + + self.verify(r'#LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n', + r'#LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n') + + def test6(self): + """ test backslash handling in an included file""" + self.verify(r'#include "backslashes.txt"', + r'\ #LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n') + + def test7(self): + """ a single \\ without using rawstrings plus many NEWLINES""" + self.verify("\ \ " + "\n\n\n\n\n\n\n\n\n", + "\ \ " + "\n\n\n\n\n\n\n\n\n") + + def test8(self): + """ single line from an apache conf file with single quotes and many NEWLINES + """ + + self.verify(r"""#LogFormat '%h %l %u %t \"%r\" %>s %b'""" + '\n\n\n\n\n\n\n', + r"""#LogFormat '%h %l %u %t \"%r\" %>s %b'""" + '\n\n\n\n\n\n\n') + +class NonTokens(OutputTest): + def test1(self): + """dollar signs not in Cheetah $vars""" + self.verify("$ $$ $5 $. $ test", + "$ $$ $5 $. $ test") + + def test2(self): + """hash not in #directives""" + self.verify("# \# #5 ", + "# # #5 ") + + def test3(self): + """escapted comments""" + self.verify(" \##escaped comment ", + " ##escaped comment ") + + def test4(self): + """escapted multi-line comments""" + self.verify(" \#*escaped comment \n*# ", + " #*escaped comment \n*# ") + + def test5(self): + """1 dollar sign""" + self.verify("$", + "$") + def _X_test6(self): + """1 dollar sign followed by hash""" + self.verify("\n$#\n", + "\n$#\n") + + def test6(self): + """1 dollar sign followed by EOL Slurp Token""" + if DEFAULT_COMPILER_SETTINGS['EOLSlurpToken']: + self.verify("\n$%s\n"%DEFAULT_COMPILER_SETTINGS['EOLSlurpToken'], + "\n$") + else: + self.verify("\n$#\n", + "\n$#\n") + +class Comments_SingleLine(OutputTest): + def test1(self): + """## followed by WS""" + self.verify("## ", + "") + + def test2(self): + """## followed by NEWLINE""" + self.verify("##\n", + "") + + def test3(self): + """## followed by text then NEWLINE""" + self.verify("## oeuao aoe uaoe \n", + "") + def test4(self): + """## gobbles leading WS""" + self.verify(" ## oeuao aoe uaoe \n", + "") + + def test5(self): + """## followed by text then NEWLINE, + leading WS""" + self.verify(" ## oeuao aoe uaoe \n", + "") + + def test6(self): + """## followed by EOF""" + self.verify("##", + "") + + def test7(self): + """## followed by EOF with leading WS""" + self.verify(" ##", + "") + + def test8(self): + """## gobble line + with text on previous and following lines""" + self.verify("line1\n ## aoeu 1234 \nline2", + "line1\nline2") + + def test9(self): + """## don't gobble line + with text on previous and following lines""" + self.verify("line1\n 12 ## aoeu 1234 \nline2", + "line1\n 12 \nline2") + + def test10(self): + """## containing $placeholders + """ + self.verify("##$a$b $c($d)", + "") + + def test11(self): + """## containing #for directive + """ + self.verify("##for $i in range(15)", + "") + + +class Comments_MultiLine_NoGobble(OutputTest): + """ + Multiline comments used to not gobble whitespace. They do now, but this can + be turned off with a compilerSetting + """ + + def _getCompilerSettings(self): + return {'gobbleWhitespaceAroundMultiLineComments':False} + + def test1(self): + """#* *# followed by WS + Shouldn't gobble WS + """ + self.verify("#* blarg *# ", + " ") + + def test2(self): + """#* *# preceded and followed by WS + Shouldn't gobble WS + """ + self.verify(" #* blarg *# ", + " ") + + def test3(self): + """#* *# followed by WS, with NEWLINE + Shouldn't gobble WS + """ + self.verify("#* \nblarg\n *# ", + " ") + + def test4(self): + """#* *# preceded and followed by WS, with NEWLINE + Shouldn't gobble WS + """ + self.verify(" #* \nblarg\n *# ", + " ") + +class Comments_MultiLine(OutputTest): + """ + Note: Multiline comments don't gobble whitespace! + """ + + def test1(self): + """#* *# followed by WS + Should gobble WS + """ + self.verify("#* blarg *# ", + "") + + def test2(self): + """#* *# preceded and followed by WS + Should gobble WS + """ + self.verify(" #* blarg *# ", + "") + + def test3(self): + """#* *# followed by WS, with NEWLINE + Shouldn't gobble WS + """ + self.verify("#* \nblarg\n *# ", + "") + + def test4(self): + """#* *# preceded and followed by WS, with NEWLINE + Shouldn't gobble WS + """ + self.verify(" #* \nblarg\n *# ", + "") + + def test5(self): + """#* *# containing nothing + """ + self.verify("#**#", + "") + + def test6(self): + """#* *# containing only NEWLINES + """ + self.verify(" #*\n\n\n\n\n\n\n\n*# ", + "") + + def test7(self): + """#* *# containing $placeholders + """ + self.verify("#* $var $var(1234*$c) *#", + "") + + def test8(self): + """#* *# containing #for directive + """ + self.verify("#* #for $i in range(15) *#", + "") + + def test9(self): + """ text around #* *# containing #for directive + """ + self.verify("foo\nfoo bar #* #for $i in range(15) *# foo\n", + "foo\nfoo bar foo\n") + + def test9(self): + """ text around #* *# containing #for directive and trailing whitespace + which should be gobbled + """ + self.verify("foo\nfoo bar #* #for $i in range(15) *# \ntest", + "foo\nfoo bar \ntest") + + def test10(self): + """ text around #* *# containing #for directive and newlines: trailing whitespace + which should be gobbled. + """ + self.verify("foo\nfoo bar #* \n\n#for $i in range(15) \n\n*# \ntest", + "foo\nfoo bar \ntest") + +class Placeholders(OutputTest): + def test1(self): + """1 placeholder""" + self.verify("$aStr", "blarg") + + def test2(self): + """2 placeholders""" + self.verify("$aStr $anInt", "blarg 1") + + def test3(self): + """2 placeholders, back-to-back""" + self.verify("$aStr$anInt", "blarg1") + + def test4(self): + """1 placeholder enclosed in ()""" + self.verify("$(aStr)", "blarg") + + def test5(self): + """1 placeholder enclosed in {}""" + self.verify("${aStr}", "blarg") + + def test6(self): + """1 placeholder enclosed in []""" + self.verify("$[aStr]", "blarg") + + def test7(self): + """1 placeholder enclosed in () + WS + + Test to make sure that $(<WS><identifier>.. matches + """ + self.verify("$( aStr )", "blarg") + + def test8(self): + """1 placeholder enclosed in {} + WS""" + self.verify("${ aStr }", "blarg") + + def test9(self): + """1 placeholder enclosed in [] + WS""" + self.verify("$[ aStr ]", "blarg") + + def test10(self): + """1 placeholder enclosed in () + WS + * cache + + Test to make sure that $*(<WS><identifier>.. matches + """ + self.verify("$*( aStr )", "blarg") + + def test11(self): + """1 placeholder enclosed in {} + WS + *cache""" + self.verify("$*{ aStr }", "blarg") + + def test12(self): + """1 placeholder enclosed in [] + WS + *cache""" + self.verify("$*[ aStr ]", "blarg") + + def test13(self): + """1 placeholder enclosed in {} + WS + *<int>*cache""" + self.verify("$*5*{ aStr }", "blarg") + + def test14(self): + """1 placeholder enclosed in [] + WS + *<int>*cache""" + self.verify("$*5*[ aStr ]", "blarg") + + def test15(self): + """1 placeholder enclosed in {} + WS + *<float>*cache""" + self.verify("$*0.5d*{ aStr }", "blarg") + + def test16(self): + """1 placeholder enclosed in [] + WS + *<float>*cache""" + self.verify("$*.5*[ aStr ]", "blarg") + + def test17(self): + """1 placeholder + *<int>*cache""" + self.verify("$*5*aStr", "blarg") + + def test18(self): + """1 placeholder *<float>*cache""" + self.verify("$*0.5h*aStr", "blarg") + + def test19(self): + """1 placeholder surrounded by single quotes and multiple newlines""" + self.verify("""'\n\n\n\n'$aStr'\n\n\n\n'""", + """'\n\n\n\n'blarg'\n\n\n\n'""") + + def test20(self): + """silent mode $!placeholders """ + self.verify("$!aStr$!nonExistant$!*nonExistant$!{nonExistant}", "blarg") + + try: + self.verify("$!aStr$nonExistant", + "blarg") + except NotFound: + pass + else: + self.fail('should raise NotFound exception') + + def test21(self): + """Make sure that $*caching is actually working""" + namesStr = 'You Me Them Everyone' + names = namesStr.split() + + tmpl = Template.compile('#for name in $names: $name ', baseclass=dict) + assert str(tmpl({'names':names})).strip()==namesStr + + tmpl = tmpl.subclass('#for name in $names: $*name ') + assert str(tmpl({'names':names}))=='You '*len(names) + + tmpl = tmpl.subclass('#for name in $names: $*1*name ') + assert str(tmpl({'names':names}))=='You '*len(names) + + tmpl = tmpl.subclass('#for name in $names: $*1*(name) ') + assert str(tmpl({'names':names}))=='You '*len(names) + + if versionTuple > (2,2): + tmpl = tmpl.subclass('#for name in $names: $*1*(name) ') + assert str(tmpl(names=names))=='You '*len(names) + +class Placeholders_Vals(OutputTest): + convertEOLs = False + def test1(self): + """string""" + self.verify("$aStr", "blarg") + + def test2(self): + """string - with whitespace""" + self.verify(" $aStr ", " blarg ") + + def test3(self): + """empty string - with whitespace""" + self.verify("$emptyString", "") + + def test4(self): + """int""" + self.verify("$anInt", "1") + + def test5(self): + """float""" + self.verify("$aFloat", "1.5") + + def test6(self): + """list""" + self.verify("$aList", "['item0', 'item1', 'item2']") + + def test7(self): + """None + + The default output filter is ReplaceNone. + """ + self.verify("$none", "") + + def test8(self): + """True, False + """ + self.verify("$True $False", "%s %s"%(repr(True), repr(False))) + + def test9(self): + """$_ + """ + self.verify("$_('foo')", "Translated: foo") + +class PlaceholderStrings(OutputTest): + def test1(self): + """some c'text $placeholder text' strings""" + self.verify("$str(c'$aStr')", "blarg") + + def test2(self): + """some c'text $placeholder text' strings""" + self.verify("$str(c'$aStr.upper')", "BLARG") + + def test3(self): + """some c'text $placeholder text' strings""" + self.verify("$str(c'$(aStr.upper.replace(c\"A$str()\",\"\"))')", "BLRG") + + def test4(self): + """some c'text $placeholder text' strings""" + self.verify("#echo $str(c'$(aStr.upper)')", "BLARG") + + def test5(self): + """some c'text $placeholder text' strings""" + self.verify("#if 1 then $str(c'$(aStr.upper)') else 0", "BLARG") + + def test6(self): + """some c'text $placeholder text' strings""" + self.verify("#if 1\n$str(c'$(aStr.upper)')#slurp\n#else\n0#end if", "BLARG") + + def test7(self): + """some c'text $placeholder text' strings""" + self.verify("#def foo(arg=c'$(\"BLARG\")')\n" + "$arg#slurp\n" + "#end def\n" + "$foo()$foo(c'$anInt')#slurp", + + "BLARG1") + + + +class UnicodeStrings(OutputTest): + def test1(self): + """unicode data in placeholder + """ + #self.verify(u"$unicodeData", defaultTestNameSpace['unicodeData'], outputEncoding='utf8') + self.verify(u"$unicodeData", defaultTestNameSpace['unicodeData']) + + def test2(self): + """unicode data in body + """ + self.verify(u"aoeu12345\u1234", u"aoeu12345\u1234") + #self.verify(u"#encoding utf8#aoeu12345\u1234", u"aoeu12345\u1234") + +class EncodingDirective(OutputTest): + def test1(self): + """basic #encoding """ + self.verify("#encoding utf-8\n1234", + "1234") + + def test2(self): + """basic #encoding """ + self.verify("#encoding ascii\n1234", + "1234") + + def test3(self): + """basic #encoding """ + self.verify("#encoding utf-8\n\xe1\x88\xb4", + u'\u1234', outputEncoding='utf8') + + def test4(self): + """basic #encoding """ + self.verify("#encoding latin-1\n\xe1\x88\xb4", + u"\xe1\x88\xb4") + + def test5(self): + """basic #encoding """ + self.verify("#encoding latin-1\nAndr\202", + u'Andr\202') + +class UnicodeDirective(OutputTest): + def test1(self): + """basic #unicode """ + self.verify("#unicode utf-8\n1234", + u"1234") + + self.verify("#unicode ascii\n1234", + u"1234") + + self.verify("#unicode latin-1\n1234", + u"1234") + + self.verify("#unicode latin-1\n1234ü", + u"1234ü") + self.verify("#unicode: latin-1\n1234ü", + u"1234ü") + self.verify("# unicode : latin-1\n1234ü", + u"1234ü") + + self.verify(u"#unicode latin-1\n1234ü", + u"1234ü") + + self.verify("#encoding latin-1\n1234ü", + u"1234ü") + +class Placeholders_Esc(OutputTest): + convertEOLs = False + def test1(self): + """1 escaped placeholder""" + self.verify("\$var", + "$var") + + def test2(self): + """2 escaped placeholders""" + self.verify("\$var \$_", + "$var $_") + + def test3(self): + """2 escaped placeholders - back to back""" + self.verify("\$var\$_", + "$var$_") + + def test4(self): + """2 escaped placeholders - nested""" + self.verify("\$var(\$_)", + "$var($_)") + + def test5(self): + """2 escaped placeholders - nested and enclosed""" + self.verify("\$(var(\$_)", + "$(var($_)") + + +class Placeholders_Calls(OutputTest): + def test1(self): + """func placeholder - no ()""" + self.verify("$aFunc", + "Scooby") + + def test2(self): + """func placeholder - with ()""" + self.verify("$aFunc()", + "Scooby") + + def test3(self): + r"""func placeholder - with (\n\n)""" + self.verify("$aFunc(\n\n)", + "Scooby", convertEOLs=False) + + def test4(self): + r"""func placeholder - with (\n\n) and $() enclosure""" + self.verify("$(aFunc(\n\n))", + "Scooby", convertEOLs=False) + + def test5(self): + r"""func placeholder - with (\n\n) and ${} enclosure""" + self.verify("${aFunc(\n\n)}", + "Scooby", convertEOLs=False) + + def test6(self): + """func placeholder - with (int)""" + self.verify("$aFunc(1234)", + "1234") + + def test7(self): + r"""func placeholder - with (\nint\n)""" + self.verify("$aFunc(\n1234\n)", + "1234", convertEOLs=False) + def test8(self): + """func placeholder - with (string)""" + self.verify("$aFunc('aoeu')", + "aoeu") + + def test9(self): + """func placeholder - with ('''string''')""" + self.verify("$aFunc('''aoeu''')", + "aoeu") + def test10(self): + r"""func placeholder - with ('''\nstring\n''')""" + self.verify("$aFunc('''\naoeu\n''')", + "\naoeu\n") + + def test11(self): + r"""func placeholder - with ('''\nstring'\n''')""" + self.verify("$aFunc('''\naoeu'\n''')", + "\naoeu'\n") + + def test12(self): + r'''func placeholder - with ("""\nstring\n""")''' + self.verify('$aFunc("""\naoeu\n""")', + "\naoeu\n") + + def test13(self): + """func placeholder - with (string*int)""" + self.verify("$aFunc('aoeu'*2)", + "aoeuaoeu") + + def test14(self): + """func placeholder - with (int*int)""" + self.verify("$aFunc(2*2)", + "4") + + def test15(self): + """func placeholder - with (int*float)""" + self.verify("$aFunc(2*2.0)", + "4.0") + + def test16(self): + r"""func placeholder - with (int\n*\nfloat)""" + self.verify("$aFunc(2\n*\n2.0)", + "4.0", convertEOLs=False) + + def test17(self): + """func placeholder - with ($arg=float)""" + self.verify("$aFunc($arg=4.0)", + "4.0") + + def test18(self): + """func placeholder - with (arg=float)""" + self.verify("$aFunc(arg=4.0)", + "4.0") + + def test19(self): + """deeply nested argstring, no enclosure""" + self.verify("$aFunc($arg=$aMeth($arg=$aFunc(1)))", + "1") + + def test20(self): + """deeply nested argstring, no enclosure + with WS""" + self.verify("$aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) )", + "1") + def test21(self): + """deeply nested argstring, () enclosure + with WS""" + self.verify("$(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )", + "1") + + def test22(self): + """deeply nested argstring, {} enclosure + with WS""" + self.verify("${aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) }", + "1") + + def test23(self): + """deeply nested argstring, [] enclosure + with WS""" + self.verify("$[aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) ]", + "1") + + def test24(self): + """deeply nested argstring, () enclosure + *cache""" + self.verify("$*(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )", + "1") + def test25(self): + """deeply nested argstring, () enclosure + *15*cache""" + self.verify("$*15*(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )", + "1") + + def test26(self): + """a function call with the Python None kw.""" + self.verify("$aFunc(None)", + "") + +class NameMapper(OutputTest): + def test1(self): + """autocalling""" + self.verify("$aFunc! $aFunc().", + "Scooby! Scooby.") + + def test2(self): + """nested autocalling""" + self.verify("$aFunc($aFunc).", + "Scooby.") + + def test3(self): + """list subscription""" + self.verify("$aList[0]", + "item0") + + def test4(self): + """list slicing""" + self.verify("$aList[:2]", + "['item0', 'item1']") + + def test5(self): + """list slicing and subcription combined""" + self.verify("$aList[:2][0]", + "item0") + + def test6(self): + """dictionary access - NameMapper style""" + self.verify("$aDict.one", + "item1") + + def test7(self): + """dictionary access - Python style""" + self.verify("$aDict['one']", + "item1") + + def test8(self): + """dictionary access combined with autocalled string method""" + self.verify("$aDict.one.upper", + "ITEM1") + + def test9(self): + """dictionary access combined with string method""" + self.verify("$aDict.one.upper()", + "ITEM1") + + def test10(self): + """nested dictionary access - NameMapper style""" + self.verify("$aDict.nestedDict.two", + "nestedItem2") + + def test11(self): + """nested dictionary access - Python style""" + self.verify("$aDict['nestedDict']['two']", + "nestedItem2") + + def test12(self): + """nested dictionary access - alternating style""" + self.verify("$aDict['nestedDict'].two", + "nestedItem2") + + def test13(self): + """nested dictionary access using method - alternating style""" + self.verify("$aDict.get('nestedDict').two", + "nestedItem2") + + def test14(self): + """nested dictionary access - NameMapper style - followed by method""" + self.verify("$aDict.nestedDict.two.upper", + "NESTEDITEM2") + + def test15(self): + """nested dictionary access - alternating style - followed by method""" + self.verify("$aDict['nestedDict'].two.upper", + "NESTEDITEM2") + + def test16(self): + """nested dictionary access - NameMapper style - followed by method, then slice""" + self.verify("$aDict.nestedDict.two.upper[:4]", + "NEST") + + def test17(self): + """nested dictionary access - Python style using a soft-coded key""" + self.verify("$aDict[$anObj.meth('nestedDict')].two", + "nestedItem2") + + def test18(self): + """object method access""" + self.verify("$anObj.meth1", + "doo") + + def test19(self): + """object method access, followed by complex slice""" + self.verify("$anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]", + "do") + + def test20(self): + """object method access, followed by a very complex slice + If it can pass this one, it's safe to say it works!!""" + self.verify("$( anObj.meth1[0:\n (\n(4/4*2)*2)/$anObj.meth1(2)\n ] )", + "do") + + def test21(self): + """object method access with % in the default arg for the meth. + + This tests a bug that Jeff Johnson found and submitted a patch to SF + for.""" + + self.verify("$anObj.methWithPercentSignDefaultArg", + "110%") + + +#class NameMapperDict(OutputTest): +# +# _searchList = [{"update": "Yabba dabba doo!"}] +# +# def test1(self): +# if NameMapper_C_VERSION: +# return # This feature is not in the C version yet. +# self.verify("$update", "Yabba dabba doo!") +# + +class CacheDirective(OutputTest): + + def test1(self): + r"""simple #cache """ + self.verify("#cache:$anInt", + "1") + + def test2(self): + r"""simple #cache + WS""" + self.verify(" #cache \n$anInt#end cache", + "1") + + def test3(self): + r"""simple #cache ... #end cache""" + self.verify("""#cache id='cache1', timer=150m +$anInt +#end cache +$aStr""", + "1\nblarg") + + def test4(self): + r"""2 #cache ... #end cache blocks""" + self.verify("""#slurp +#def foo +#cache ID='cache1', timer=150m +$anInt +#end cache +#cache id='cache2', timer=15s + #for $i in range(5) +$i#slurp + #end for +#end cache +$aStr#slurp +#end def +$foo$foo$foo$foo$foo""", + "1\n01234blarg"*5) + + + def test5(self): + r"""nested #cache blocks""" + self.verify("""#slurp +#def foo +#cache ID='cache1', timer=150m +$anInt +#cache id='cache2', timer=15s + #for $i in range(5) +$i#slurp + #end for +$*(6)#slurp +#end cache +#end cache +$aStr#slurp +#end def +$foo$foo$foo$foo$foo""", + "1\n012346blarg"*5) + + def test6(self): + r"""Make sure that partial directives don't match""" + self.verify("#cache_foo", + "#cache_foo") + self.verify("#cached", + "#cached") + +class CallDirective(OutputTest): + + def test1(self): + r"""simple #call """ + self.verify("#call int\n$anInt#end call", + "1") + # single line version + self.verify("#call int: $anInt", + "1") + self.verify("#call int: 10\n$aStr", + "10\nblarg") + + def test2(self): + r"""simple #call + WS""" + self.verify("#call int\n$anInt #end call", + "1") + + def test3(self): + r"""a longer #call""" + self.verify('''\ +#def meth(arg) +$arg.upper()#slurp +#end def +#call $meth +$(1234+1) foo#slurp +#end call''', + "1235 FOO") + + def test4(self): + r"""#call with keyword #args""" + self.verify('''\ +#def meth(arg1, arg2) +$arg1.upper() - $arg2.lower()#slurp +#end def +#call self.meth +#arg arg1 +$(1234+1) foo#slurp +#arg arg2 +UPPER#slurp +#end call''', + "1235 FOO - upper") + + def test5(self): + r"""#call with single-line keyword #args """ + self.verify('''\ +#def meth(arg1, arg2) +$arg1.upper() - $arg2.lower()#slurp +#end def +#call self.meth +#arg arg1:$(1234+1) foo#slurp +#arg arg2:UPPER#slurp +#end call''', + "1235 FOO - upper") + + def test6(self): + """#call with python kwargs and cheetah output for the 1s positional + arg""" + + self.verify('''\ +#def meth(arg1, arg2) +$arg1.upper() - $arg2.lower()#slurp +#end def +#call self.meth arg2="UPPER" +$(1234+1) foo#slurp +#end call''', + "1235 FOO - upper") + + def test7(self): + """#call with python kwargs and #args""" + self.verify('''\ +#def meth(arg1, arg2, arg3) +$arg1.upper() - $arg2.lower() - $arg3#slurp +#end def +#call self.meth arg2="UPPER", arg3=999 +#arg arg1:$(1234+1) foo#slurp +#end call''', + "1235 FOO - upper - 999") + + def test8(self): + """#call with python kwargs and #args, and using a function to get the + function that will be called""" + self.verify('''\ +#def meth(arg1, arg2, arg3) +$arg1.upper() - $arg2.lower() - $arg3#slurp +#end def +#call getattr(self, "meth") arg2="UPPER", arg3=999 +#arg arg1:$(1234+1) foo#slurp +#end call''', + "1235 FOO - upper - 999") + + def test9(self): + """nested #call directives""" + self.verify('''\ +#def meth(arg1) +$arg1#slurp +#end def +#def meth2(x,y) +$x$y#slurp +#end def +## +#call self.meth +1#slurp +#call self.meth +2#slurp +#call self.meth +3#slurp +#end call 3 +#set two = 2 +#call self.meth2 y=c"$(10/$two)" +#arg x +4#slurp +#end call 4 +#end call 2 +#end call 1''', + "12345") + + + +class I18nDirective(OutputTest): + def test1(self): + r"""simple #call """ + self.verify("#i18n \n$anInt#end i18n", + "1") + + # single line version + self.verify("#i18n: $anInt", + "1") + self.verify("#i18n: 10\n$aStr", + "10\nblarg") + + +class CaptureDirective(OutputTest): + def test1(self): + r"""simple #capture""" + self.verify('''\ +#capture cap1 +$(1234+1) foo#slurp +#end capture +$cap1#slurp +''', + "1235 foo") + + + def test2(self): + r"""slightly more complex #capture""" + self.verify('''\ +#def meth(arg) +$arg.upper()#slurp +#end def +#capture cap1 +$(1234+1) $anInt $meth("foo")#slurp +#end capture +$cap1#slurp +''', + "1235 1 FOO") + + +class SlurpDirective(OutputTest): + def test1(self): + r"""#slurp with 1 \n """ + self.verify("#slurp\n", + "") + + def test2(self): + r"""#slurp with 1 \n, leading whitespace + Should gobble""" + self.verify(" #slurp\n", + "") + + def test3(self): + r"""#slurp with 1 \n, leading content + Shouldn't gobble""" + self.verify(" 1234 #slurp\n", + " 1234 ") + + def test4(self): + r"""#slurp with WS then \n, leading content + Shouldn't gobble""" + self.verify(" 1234 #slurp \n", + " 1234 ") + + def test5(self): + r"""#slurp with garbage chars then \n, leading content + Should eat the garbage""" + self.verify(" 1234 #slurp garbage \n", + " 1234 ") + + + +class EOLSlurpToken(OutputTest): + _EOLSlurpToken = DEFAULT_COMPILER_SETTINGS['EOLSlurpToken'] + def test1(self): + r"""#slurp with 1 \n """ + self.verify("%s\n"%self._EOLSlurpToken, + "") + + def test2(self): + r"""#slurp with 1 \n, leading whitespace + Should gobble""" + self.verify(" %s\n"%self._EOLSlurpToken, + "") + def test3(self): + r"""#slurp with 1 \n, leading content + Shouldn't gobble""" + self.verify(" 1234 %s\n"%self._EOLSlurpToken, + " 1234 ") + + def test4(self): + r"""#slurp with WS then \n, leading content + Shouldn't gobble""" + self.verify(" 1234 %s \n"%self._EOLSlurpToken, + " 1234 ") + + def test5(self): + r"""#slurp with garbage chars then \n, leading content + Should NOT eat the garbage""" + self.verify(" 1234 %s garbage \n"%self._EOLSlurpToken, + " 1234 %s garbage \n"%self._EOLSlurpToken) + +if not DEFAULT_COMPILER_SETTINGS['EOLSlurpToken']: + del EOLSlurpToken + +class RawDirective(OutputTest): + def test1(self): + """#raw till EOF""" + self.verify("#raw\n$aFunc().\n\n", + "$aFunc().\n\n") + + def test2(self): + """#raw till #end raw""" + self.verify("#raw\n$aFunc().\n#end raw\n$anInt", + "$aFunc().\n1") + + def test3(self): + """#raw till #end raw gobble WS""" + self.verify(" #raw \n$aFunc().\n #end raw \n$anInt", + "$aFunc().\n1") + + def test4(self): + """#raw till #end raw using explicit directive closure + Shouldn't gobble""" + self.verify(" #raw #\n$aFunc().\n #end raw #\n$anInt", + " \n$aFunc().\n\n1") + + def test5(self): + """single-line short form #raw: """ + self.verify("#raw: $aFunc().\n\n", + "$aFunc().\n\n") + + self.verify("#raw: $aFunc().\n$anInt", + "$aFunc().\n1") + +class BreakpointDirective(OutputTest): + def test1(self): + """#breakpoint part way through source code""" + self.verify("$aFunc(2).\n#breakpoint\n$anInt", + "2.\n") + + def test2(self): + """#breakpoint at BOF""" + self.verify("#breakpoint\n$anInt", + "") + + def test3(self): + """#breakpoint at EOF""" + self.verify("$anInt\n#breakpoint", + "1\n") + + +class StopDirective(OutputTest): + def test1(self): + """#stop part way through source code""" + self.verify("$aFunc(2).\n#stop\n$anInt", + "2.\n") + + def test2(self): + """#stop at BOF""" + self.verify("#stop\n$anInt", + "") + + def test3(self): + """#stop at EOF""" + self.verify("$anInt\n#stop", + "1\n") + + def test4(self): + """#stop in pos test block""" + self.verify("""$anInt +#if 1 +inside the if block +#stop +#end if +blarg""", + "1\ninside the if block\n") + + def test5(self): + """#stop in neg test block""" + self.verify("""$anInt +#if 0 +inside the if block +#stop +#end if +blarg""", + "1\nblarg") + + +class ReturnDirective(OutputTest): + + def test1(self): + """#return'ing an int """ + self.verify("""1 +$str($test-6) +3 +#def test +#if 1 +#return (3 *2) \ + + 2 +#else +aoeuoaeu +#end if +#end def +""", + "1\n2\n3\n") + + def test2(self): + """#return'ing an string """ + self.verify("""1 +$str($test[1]) +3 +#def test +#if 1 +#return '123' +#else +aoeuoaeu +#end if +#end def +""", + "1\n2\n3\n") + + def test3(self): + """#return'ing an string AND streaming other output via the transaction""" + self.verify("""1 +$str($test(trans=trans)[1]) +3 +#def test +1.5 +#if 1 +#return '123' +#else +aoeuoaeu +#end if +#end def +""", + "1\n1.5\n2\n3\n") + + +class YieldDirective(OutputTest): + convertEOLs = False + def test1(self): + """simple #yield """ + + src1 = """#for i in range(10)\n#yield i\n#end for""" + src2 = """#for i in range(10)\n$i#slurp\n#yield\n#end for""" + src3 = ("#def iterator\n" + "#for i in range(10)\n#yield i\n#end for\n" + "#end def\n" + "#for i in $iterator\n$i#end for" + ) + + + for src in (src1,src2,src3): + klass = Template.compile(src, keepRefToGeneratedCode=True) + #print klass._CHEETAH_generatedModuleCode + iter = klass().respond() + output = [str(i) for i in iter] + assert ''.join(output)=='0123456789' + #print ''.join(output) + + # @@TR: need to expand this to cover error conditions etc. + +if versionTuple < (2,3): + del YieldDirective + +class ForDirective(OutputTest): + + def test1(self): + """#for loop with one local var""" + self.verify("#for $i in range(5)\n$i\n#end for", + "0\n1\n2\n3\n4\n") + + self.verify("#for $i in range(5):\n$i\n#end for", + "0\n1\n2\n3\n4\n") + + self.verify("#for $i in range(5): ##comment\n$i\n#end for", + "0\n1\n2\n3\n4\n") + + self.verify("#for $i in range(5) ##comment\n$i\n#end for", + "0\n1\n2\n3\n4\n") + + + def test2(self): + """#for loop with WS in loop""" + self.verify("#for $i in range(5)\n$i \n#end for", + "0 \n1 \n2 \n3 \n4 \n") + + def test3(self): + """#for loop gobble WS""" + self.verify(" #for $i in range(5) \n$i \n #end for ", + "0 \n1 \n2 \n3 \n4 \n") + + def test4(self): + """#for loop over list""" + self.verify("#for $i, $j in [(0,1),(2,3)]\n$i,$j\n#end for", + "0,1\n2,3\n") + + def test5(self): + """#for loop over list, with #slurp""" + self.verify("#for $i, $j in [(0,1),(2,3)]\n$i,$j#slurp\n#end for", + "0,12,3") + + def test6(self): + """#for loop with explicit closures""" + self.verify("#for $i in range(5)#$i#end for#", + "01234") + + def test7(self): + """#for loop with explicit closures and WS""" + self.verify(" #for $i in range(5)#$i#end for# ", + " 01234 ") + + def test8(self): + """#for loop using another $var""" + self.verify(" #for $i in range($aFunc(5))#$i#end for# ", + " 01234 ") + + def test9(self): + """test methods in for loops""" + self.verify("#for $func in $listOfLambdas\n$func($anInt)\n#end for", + "1\n1\n1\n") + + + def test10(self): + """#for loop over list, using methods of the items""" + self.verify("#for i, j in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for", + "AA,BB\nCC,DD\n") + self.verify("#for $i, $j in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for", + "AA,BB\nCC,DD\n") + + def test11(self): + """#for loop over list, using ($i,$j) style target list""" + self.verify("#for (i, j) in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for", + "AA,BB\nCC,DD\n") + self.verify("#for ($i, $j) in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for", + "AA,BB\nCC,DD\n") + + def test12(self): + """#for loop over list, using i, (j,k) style target list""" + self.verify("#for i, (j, k) in enumerate([('aa','bb'),('cc','dd')])\n$j.upper,$k.upper\n#end for", + "AA,BB\nCC,DD\n") + self.verify("#for $i, ($j, $k) in enumerate([('aa','bb'),('cc','dd')])\n$j.upper,$k.upper\n#end for", + "AA,BB\nCC,DD\n") + + def test13(self): + """single line #for""" + self.verify("#for $i in range($aFunc(5)): $i", + "01234") + + def test14(self): + """single line #for with 1 extra leading space""" + self.verify("#for $i in range($aFunc(5)): $i", + " 0 1 2 3 4") + + def test15(self): + """2 times single line #for""" + self.verify("#for $i in range($aFunc(5)): $i#slurp\n"*2, + "01234"*2) + + def test16(self): + """false single line #for """ + self.verify("#for $i in range(5): \n$i\n#end for", + "0\n1\n2\n3\n4\n") + +if versionTuple < (2,3): + del ForDirective.test12 + +class RepeatDirective(OutputTest): + + def test1(self): + """basic #repeat""" + self.verify("#repeat 3\n1\n#end repeat", + "1\n1\n1\n") + self.verify("#repeat 3: \n1\n#end repeat", + "1\n1\n1\n") + + self.verify("#repeat 3 ##comment\n1\n#end repeat", + "1\n1\n1\n") + + self.verify("#repeat 3: ##comment\n1\n#end repeat", + "1\n1\n1\n") + + def test2(self): + """#repeat with numeric expression""" + self.verify("#repeat 3*3/3\n1\n#end repeat", + "1\n1\n1\n") + + def test3(self): + """#repeat with placeholder""" + self.verify("#repeat $numTwo\n1\n#end repeat", + "1\n1\n") + + def test4(self): + """#repeat with placeholder * num""" + self.verify("#repeat $numTwo*1\n1\n#end repeat", + "1\n1\n") + + def test5(self): + """#repeat with placeholder and WS""" + self.verify(" #repeat $numTwo \n1\n #end repeat ", + "1\n1\n") + + def test6(self): + """single-line #repeat""" + self.verify("#repeat $numTwo: 1", + "11") + self.verify("#repeat $numTwo: 1\n"*2, + "1\n1\n"*2) + + #false single-line + self.verify("#repeat 3: \n1\n#end repeat", + "1\n1\n1\n") + + +class AttrDirective(OutputTest): + + def test1(self): + """#attr with int""" + self.verify("#attr $test = 1234\n$test", + "1234") + + def test2(self): + """#attr with string""" + self.verify("#attr $test = 'blarg'\n$test", + "blarg") + + def test3(self): + """#attr with expression""" + self.verify("#attr $test = 'blarg'.upper()*2\n$test", + "BLARGBLARG") + + def test4(self): + """#attr with string + WS + Should gobble""" + self.verify(" #attr $test = 'blarg' \n$test", + "blarg") + + def test5(self): + """#attr with string + WS + leading text + Shouldn't gobble""" + self.verify(" -- #attr $test = 'blarg' \n$test", + " -- \nblarg") + + +class DefDirective(OutputTest): + + def test1(self): + """#def without argstring""" + self.verify("#def testMeth\n1234\n#end def\n$testMeth", + "1234\n") + + self.verify("#def testMeth ## comment\n1234\n#end def\n$testMeth", + "1234\n") + + self.verify("#def testMeth: ## comment\n1234\n#end def\n$testMeth", + "1234\n") + + def test2(self): + """#def without argstring, gobble WS""" + self.verify(" #def testMeth \n1234\n #end def \n$testMeth", + "1234\n") + + def test3(self): + """#def with argstring, gobble WS""" + self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth", + "1234-999\n") + + def test4(self): + """#def with argstring, gobble WS, string used in call""" + self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth('ABC')", + "1234-ABC\n") + + def test5(self): + """#def with argstring, gobble WS, list used in call""" + self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth([1,2,3])", + "1234-[1, 2, 3]\n") + + def test6(self): + """#def with 2 args, gobble WS, list used in call""" + self.verify(" #def testMeth($a, $b='default') \n1234-$a$b\n #end def\n$testMeth([1,2,3])", + "1234-[1, 2, 3]default\n") + + def test7(self): + """#def with *args, gobble WS""" + self.verify(" #def testMeth($*args) \n1234-$args\n #end def\n$testMeth", + "1234-()\n") + + def test8(self): + """#def with **KWs, gobble WS""" + self.verify(" #def testMeth($**KWs) \n1234-$KWs\n #end def\n$testMeth", + "1234-{}\n") + + def test9(self): + """#def with *args + **KWs, gobble WS""" + self.verify(" #def testMeth($*args, $**KWs) \n1234-$args-$KWs\n #end def\n$testMeth", + "1234-()-{}\n") + + def test10(self): + """#def with *args + **KWs, gobble WS""" + self.verify( + " #def testMeth($*args, $**KWs) \n1234-$args-$KWs.a\n #end def\n$testMeth(1,2, a=1)", + "1234-(1, 2)-1\n") + + + def test11(self): + """single line #def with extra WS""" + self.verify( + "#def testMeth: aoeuaoeu\n- $testMeth -", + "- aoeuaoeu -") + + def test12(self): + """single line #def with extra WS and nested $placeholders""" + self.verify( + "#def testMeth: $anInt $aFunc(1234)\n- $testMeth -", + "- 1 1234 -") + + def test13(self): + """single line #def escaped $placeholders""" + self.verify( + "#def testMeth: \$aFunc(\$anInt)\n- $testMeth -", + "- $aFunc($anInt) -") + + def test14(self): + """single line #def 1 escaped $placeholders""" + self.verify( + "#def testMeth: \$aFunc($anInt)\n- $testMeth -", + "- $aFunc(1) -") + + def test15(self): + """single line #def 1 escaped $placeholders + more WS""" + self.verify( + "#def testMeth : \$aFunc($anInt)\n- $testMeth -", + "- $aFunc(1) -") + + def test16(self): + """multiline #def with $ on methodName""" + self.verify("#def $testMeth\n1234\n#end def\n$testMeth", + "1234\n") + + def test17(self): + """single line #def with $ on methodName""" + self.verify("#def $testMeth:1234\n$testMeth", + "1234") + + def test18(self): + """single line #def with an argument""" + self.verify("#def $testMeth($arg=1234):$arg\n$testMeth", + "1234") + + def test19(self): + """#def that extends over two lines with arguments""" + self.verify("#def $testMeth($arg=1234,\n" + +" $arg2=5678)\n" + +"$arg $arg2\n" + +"#end def\n" + +"$testMeth", + "1234 5678\n") + +class DecoratorDirective(OutputTest): + def test1(self): + """single line #def with decorator""" + + self.verify("#@ blah", "#@ blah") + self.verify("#@23 blah", "#@23 blah") + self.verify("#@@TR: comment", "#@@TR: comment") + + self.verify("#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n" + +"#@testdecorator" + +"\n#def $testMeth():1234\n$testMeth", + + "1234") + + self.verify("#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n" + +"#@testdecorator" + +"\n#block $testMeth():1234", + + "1234") + + try: + self.verify( + "#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n" + +"#@testdecorator\n sdf" + +"\n#def $testMeth():1234\n$testMeth", + + "1234") + except ParseError: + pass + else: + self.fail('should raise a ParseError') + + def test2(self): + """#def with multiple decorators""" + self.verify("#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n" + +"#@testdecorator\n" + +"#@testdecorator\n" + +"#def testMeth\n" + +"1234\n" + "#end def\n" + "$testMeth", + "1234\n") + +if versionTuple < (2,4): + del DecoratorDirective + +class BlockDirective(OutputTest): + + def test1(self): + """#block without argstring""" + self.verify("#block testBlock\n1234\n#end block", + "1234\n") + + self.verify("#block testBlock ##comment\n1234\n#end block", + "1234\n") + + def test2(self): + """#block without argstring, gobble WS""" + self.verify(" #block testBlock \n1234\n #end block ", + "1234\n") + + def test3(self): + """#block with argstring, gobble WS + + Because blocks can be reused in multiple parts of the template arguments + (!!with defaults!!) can be given.""" + + self.verify(" #block testBlock($a=999) \n1234-$a\n #end block ", + "1234-999\n") + + def test4(self): + """#block with 2 args, gobble WS""" + self.verify(" #block testBlock($a=999, $b=444) \n1234-$a$b\n #end block ", + "1234-999444\n") + + + def test5(self): + """#block with 2 nested blocks + + Blocks can be nested to any depth and the name of the block is optional + for the #end block part: #end block OR #end block [name] """ + + self.verify("""#block testBlock +this is a test block +#block outerNest +outer +#block innerNest +inner +#end block innerNest +#end block outerNest +--- +#end block testBlock +""", + "this is a test block\nouter\ninner\n---\n") + + + def test6(self): + """single line #block """ + self.verify( + "#block testMeth: This is my block", + "This is my block") + + def test7(self): + """single line #block with WS""" + self.verify( + "#block testMeth: This is my block", + "This is my block") + + def test8(self): + """single line #block 1 escaped $placeholders""" + self.verify( + "#block testMeth: \$aFunc($anInt)", + "$aFunc(1)") + + def test9(self): + """single line #block 1 escaped $placeholders + WS""" + self.verify( + "#block testMeth: \$aFunc( $anInt )", + "$aFunc( 1 )") + + def test10(self): + """single line #block 1 escaped $placeholders + more WS""" + self.verify( + "#block testMeth : \$aFunc( $anInt )", + "$aFunc( 1 )") + + def test11(self): + """multiline #block $ on argstring""" + self.verify("#block $testBlock\n1234\n#end block", + "1234\n") + + def test12(self): + """single line #block with $ on methodName """ + self.verify( + "#block $testMeth: This is my block", + "This is my block") + + def test13(self): + """single line #block with an arg """ + self.verify( + "#block $testMeth($arg='This is my block'): $arg", + "This is my block") + + def test14(self): + """single line #block with None for content""" + self.verify( + """#block $testMeth: $None\ntest $testMeth-""", + "test -") + + def test15(self): + """single line #block with nothing for content""" + self.verify( + """#block $testMeth: \nfoo\n#end block\ntest $testMeth-""", + "foo\ntest foo\n-") + +class IncludeDirective(OutputTest): + + def setUp(self): + fp = open('parseTest.txt','w') + fp.write("$numOne $numTwo") + fp.flush() + fp.close + + def tearDown(self): + if os.path.exists('parseTest.txt'): + os.remove('parseTest.txt') + + def test1(self): + """#include raw of source $emptyString""" + self.verify("#include raw source=$emptyString", + "") + + def test2(self): + """#include raw of source $blockToBeParsed""" + self.verify("#include raw source=$blockToBeParsed", + "$numOne $numTwo") + + def test3(self): + """#include raw of 'parseTest.txt'""" + self.verify("#include raw 'parseTest.txt'", + "$numOne $numTwo") + + def test4(self): + """#include raw of $includeFileName""" + self.verify("#include raw $includeFileName", + "$numOne $numTwo") + + def test5(self): + """#include raw of $includeFileName, with WS""" + self.verify(" #include raw $includeFileName ", + "$numOne $numTwo") + + def test6(self): + """#include raw of source= , with WS""" + self.verify(" #include raw source='This is my $Source '*2 ", + "This is my $Source This is my $Source ") + + def test7(self): + """#include of $blockToBeParsed""" + self.verify("#include source=$blockToBeParsed", + "1 2") + + def test8(self): + """#include of $blockToBeParsed, with WS""" + self.verify(" #include source=$blockToBeParsed ", + "1 2") + + def test9(self): + """#include of 'parseTest.txt', with WS""" + self.verify(" #include source=$blockToBeParsed ", + "1 2") + + def test10(self): + """#include of "parseTest.txt", with WS""" + self.verify(" #include source=$blockToBeParsed ", + "1 2") + + def test11(self): + """#include of 'parseTest.txt', with WS and surrounding text""" + self.verify("aoeu\n #include source=$blockToBeParsed \naoeu", + "aoeu\n1 2aoeu") + + def test12(self): + """#include of 'parseTest.txt', with WS and explicit closure""" + self.verify(" #include source=$blockToBeParsed# ", + " 1 2 ") + + +class SilentDirective(OutputTest): + + def test1(self): + """simple #silent""" + self.verify("#silent $aFunc", + "") + + def test2(self): + """simple #silent""" + self.verify("#silent $anObj.callIt\n$anObj.callArg", + "1234") + + self.verify("#silent $anObj.callIt ##comment\n$anObj.callArg", + "1234") + + def test3(self): + """simple #silent""" + self.verify("#silent $anObj.callIt(99)\n$anObj.callArg", + "99") + +class SetDirective(OutputTest): + + def test1(self): + """simple #set""" + self.verify("#set $testVar = 'blarg'\n$testVar", + "blarg") + self.verify("#set testVar = 'blarg'\n$testVar", + "blarg") + + + self.verify("#set testVar = 'blarg'##comment\n$testVar", + "blarg") + + def test2(self): + """simple #set with no WS between operands""" + self.verify("#set $testVar='blarg'", + "") + def test3(self): + """#set + use of var""" + self.verify("#set $testVar = 'blarg'\n$testVar", + "blarg") + + def test4(self): + """#set + use in an #include""" + self.verify("#set global $aSetVar = 1234\n#include source=$includeBlock2", + "1 2 1234") + + def test5(self): + """#set with a dictionary""" + self.verify( """#set $testDict = {'one':'one1','two':'two2','three':'three3'} +$testDict.one +$testDict.two""", + "one1\ntwo2") + + def test6(self): + """#set with string, then used in #if block""" + + self.verify("""#set $test='a string'\n#if $test#blarg#end if""", + "blarg") + + def test7(self): + """simple #set, gobble WS""" + self.verify(" #set $testVar = 'blarg' ", + "") + + def test8(self): + """simple #set, don't gobble WS""" + self.verify(" #set $testVar = 'blarg'#---", + " ---") + + def test9(self): + """simple #set with a list""" + self.verify(" #set $testVar = [1, 2, 3] \n$testVar", + "[1, 2, 3]") + + def test10(self): + """simple #set global with a list""" + self.verify(" #set global $testVar = [1, 2, 3] \n$testVar", + "[1, 2, 3]") + + def test11(self): + """simple #set global with a list and *cache + + Caching only works with global #set vars. Local vars are not accesible + to the cache namespace. + """ + + self.verify(" #set global $testVar = [1, 2, 3] \n$*testVar", + "[1, 2, 3]") + + def test12(self): + """simple #set global with a list and *<int>*cache""" + self.verify(" #set global $testVar = [1, 2, 3] \n$*5*testVar", + "[1, 2, 3]") + + def test13(self): + """simple #set with a list and *<float>*cache""" + self.verify(" #set global $testVar = [1, 2, 3] \n$*.5*testVar", + "[1, 2, 3]") + + def test14(self): + """simple #set without NameMapper on""" + self.verify("""#compiler useNameMapper = 0\n#set $testVar = 1 \n$testVar""", + "1") + + def test15(self): + """simple #set without $""" + self.verify("""#set testVar = 1 \n$testVar""", + "1") + + def test16(self): + """simple #set global without $""" + self.verify("""#set global testVar = 1 \n$testVar""", + "1") + + def test17(self): + """simple #set module without $""" + self.verify("""#set module __foo__ = 'bar'\n$__foo__""", + "bar") + + def test18(self): + """#set with i,j=list style assignment""" + self.verify("""#set i,j = [1,2]\n$i$j""", + "12") + self.verify("""#set $i,$j = [1,2]\n$i$j""", + "12") + + def test19(self): + """#set with (i,j)=list style assignment""" + self.verify("""#set (i,j) = [1,2]\n$i$j""", + "12") + self.verify("""#set ($i,$j) = [1,2]\n$i$j""", + "12") + + def test20(self): + """#set with i, (j,k)=list style assignment""" + self.verify("""#set i, (j,k) = [1,(2,3)]\n$i$j$k""", + "123") + self.verify("""#set $i, ($j,$k) = [1,(2,3)]\n$i$j$k""", + "123") + + +class IfDirective(OutputTest): + + def test1(self): + """simple #if block""" + self.verify("#if 1\n$aStr\n#end if\n", + "blarg\n") + + self.verify("#if 1:\n$aStr\n#end if\n", + "blarg\n") + + self.verify("#if 1: \n$aStr\n#end if\n", + "blarg\n") + + self.verify("#if 1: ##comment \n$aStr\n#end if\n", + "blarg\n") + + self.verify("#if 1 ##comment \n$aStr\n#end if\n", + "blarg\n") + + self.verify("#if 1##for i in range(10)#$i#end for##end if", + '0123456789') + + self.verify("#if 1: #for i in range(10)#$i#end for", + '0123456789') + + self.verify("#if 1: #for i in range(10):$i", + '0123456789') + + def test2(self): + """simple #if block, with WS""" + self.verify(" #if 1\n$aStr\n #end if \n", + "blarg\n") + def test3(self): + """simple #if block, with WS and explicit closures""" + self.verify(" #if 1#\n$aStr\n #end if #--\n", + " \nblarg\n --\n") + + def test4(self): + """#if block using $numOne""" + self.verify("#if $numOne\n$aStr\n#end if\n", + "blarg\n") + + def test5(self): + """#if block using $zero""" + self.verify("#if $zero\n$aStr\n#end if\n", + "") + def test6(self): + """#if block using $emptyString""" + self.verify("#if $emptyString\n$aStr\n#end if\n", + "") + def test7(self): + """#if ... #else ... block using a $emptyString""" + self.verify("#if $emptyString\n$anInt\n#else\n$anInt - $anInt\n#end if", + "1 - 1\n") + + def test8(self): + """#if ... #elif ... #else ... block using a $emptyString""" + self.verify("#if $emptyString\n$c\n#elif $numOne\n$numOne\n#else\n$c - $c\n#end if", + "1\n") + + def test9(self): + """#if 'not' test, with #slurp""" + self.verify("#if not $emptyString\n$aStr#slurp\n#end if\n", + "blarg") + + def test10(self): + """#if block using $*emptyString + + This should barf + """ + try: + self.verify("#if $*emptyString\n$aStr\n#end if\n", + "") + except ParseError: + pass + else: + self.fail('This should barf') + + def test11(self): + """#if block using invalid top-level $(placeholder) syntax - should barf""" + + for badSyntax in ("#if $*5*emptyString\n$aStr\n#end if\n", + "#if ${emptyString}\n$aStr\n#end if\n", + "#if $(emptyString)\n$aStr\n#end if\n", + "#if $[emptyString]\n$aStr\n#end if\n", + "#if $!emptyString\n$aStr\n#end if\n", + ): + try: + self.verify(badSyntax, "") + except ParseError: + pass + else: + self.fail('This should barf') + + def test12(self): + """#if ... #else if ... #else ... block using a $emptyString + Same as test 8 but using else if instead of elif""" + self.verify("#if $emptyString\n$c\n#else if $numOne\n$numOne\n#else\n$c - $c\n#end if", + "1\n") + + + def test13(self): + """#if# ... #else # ... block using a $emptyString with """ + self.verify("#if $emptyString# $anInt#else#$anInt - $anInt#end if", + "1 - 1") + + def test14(self): + """single-line #if: simple""" + self.verify("#if $emptyString then 'true' else 'false'", + "false") + + def test15(self): + """single-line #if: more complex""" + self.verify("#if $anInt then 'true' else 'false'", + "true") + + def test16(self): + """single-line #if: with the words 'else' and 'then' in the output """ + self.verify("#if ($anInt and not $emptyString==''' else ''') then $str('then') else 'else'", + "then") + + def test17(self): + """single-line #if: """ + self.verify("#if 1: foo\n#if 0: bar\n#if 1: foo", + "foo\nfoo") + + + self.verify("#if 1: foo\n#if 0: bar\n#if 1: foo", + "foo\nfoo") + + def test18(self): + """single-line #if: \n#else: """ + self.verify("#if 1: foo\n#elif 0: bar", + "foo\n") + + self.verify("#if 1: foo\n#elif 0: bar\n#else: blarg\n", + "foo\n") + + self.verify("#if 0: foo\n#elif 0: bar\n#else: blarg\n", + "blarg\n") + +class UnlessDirective(OutputTest): + + def test1(self): + """#unless 1""" + self.verify("#unless 1\n 1234 \n#end unless", + "") + + self.verify("#unless 1:\n 1234 \n#end unless", + "") + + self.verify("#unless 1: ##comment\n 1234 \n#end unless", + "") + + self.verify("#unless 1 ##comment\n 1234 \n#end unless", + "") + + + def test2(self): + """#unless 0""" + self.verify("#unless 0\n 1234 \n#end unless", + " 1234 \n") + + def test3(self): + """#unless $none""" + self.verify("#unless $none\n 1234 \n#end unless", + " 1234 \n") + + def test4(self): + """#unless $numTwo""" + self.verify("#unless $numTwo\n 1234 \n#end unless", + "") + + def test5(self): + """#unless $numTwo with WS""" + self.verify(" #unless $numTwo \n 1234 \n #end unless ", + "") + + def test6(self): + """single-line #unless""" + self.verify("#unless 1: 1234", "") + self.verify("#unless 0: 1234", "1234") + self.verify("#unless 0: 1234\n"*2, "1234\n"*2) + +class PSP(OutputTest): + + def test1(self): + """simple <%= [int] %>""" + self.verify("<%= 1234 %>", "1234") + + def test2(self): + """simple <%= [string] %>""" + self.verify("<%= 'blarg' %>", "blarg") + + def test3(self): + """simple <%= None %>""" + self.verify("<%= None %>", "") + def test4(self): + """simple <%= [string] %> + $anInt""" + self.verify("<%= 'blarg' %>$anInt", "blarg1") + + def test5(self): + """simple <%= [EXPR] %> + $anInt""" + self.verify("<%= ('blarg'*2).upper() %>$anInt", "BLARGBLARG1") + + def test6(self): + """for loop in <%%>""" + self.verify("<% for i in range(5):%>1<%end%>", "11111") + + def test7(self): + """for loop in <%%> and using <%=i%>""" + self.verify("<% for i in range(5):%><%=i%><%end%>", "01234") + + def test8(self): + """for loop in <% $%> and using <%=i%>""" + self.verify("""<% for i in range(5): + i=i*2$%><%=i%><%end%>""", "02468") + + def test9(self): + """for loop in <% $%> and using <%=i%> plus extra text""" + self.verify("""<% for i in range(5): + i=i*2$%><%=i%>-<%end%>""", "0-2-4-6-8-") + + +class WhileDirective(OutputTest): + def test1(self): + """simple #while with a counter""" + self.verify("#set $i = 0\n#while $i < 5\n$i#slurp\n#set $i += 1\n#end while", + "01234") + +class ContinueDirective(OutputTest): + def test1(self): + """#continue with a #while""" + self.verify("""#set $i = 0 +#while $i < 5 +#if $i == 3 + #set $i += 1 + #continue +#end if +$i#slurp +#set $i += 1 +#end while""", + "0124") + + def test2(self): + """#continue with a #for""" + self.verify("""#for $i in range(5) +#if $i == 3 + #continue +#end if +$i#slurp +#end for""", + "0124") + +class BreakDirective(OutputTest): + def test1(self): + """#break with a #while""" + self.verify("""#set $i = 0 +#while $i < 5 +#if $i == 3 + #break +#end if +$i#slurp +#set $i += 1 +#end while""", + "012") + + def test2(self): + """#break with a #for""" + self.verify("""#for $i in range(5) +#if $i == 3 + #break +#end if +$i#slurp +#end for""", + "012") + + +class TryDirective(OutputTest): + + def test1(self): + """simple #try + """ + self.verify("#try\n1234\n#except\nblarg\n#end try", + "1234\n") + + def test2(self): + """#try / #except with #raise + """ + self.verify("#try\n#raise ValueError\n#except\nblarg\n#end try", + "blarg\n") + + def test3(self): + """#try / #except with #raise + WS + + Should gobble + """ + self.verify(" #try \n #raise ValueError \n #except \nblarg\n #end try", + "blarg\n") + + + def test4(self): + """#try / #except with #raise + WS and leading text + + Shouldn't gobble + """ + self.verify("--#try \n #raise ValueError \n #except \nblarg\n #end try#--", + "--\nblarg\n --") + + def test5(self): + """nested #try / #except with #raise + """ + self.verify( +"""#try + #raise ValueError +#except + #try + #raise ValueError + #except +blarg + #end try +#end try""", + "blarg\n") + +class PassDirective(OutputTest): + def test1(self): + """#pass in a #try / #except block + """ + self.verify("#try\n#raise ValueError\n#except\n#pass\n#end try", + "") + + def test2(self): + """#pass in a #try / #except block + WS + """ + self.verify(" #try \n #raise ValueError \n #except \n #pass \n #end try", + "") + + +class AssertDirective(OutputTest): + def test1(self): + """simple #assert + """ + self.verify("#set $x = 1234\n#assert $x == 1234", + "") + + def test2(self): + """simple #assert that fails + """ + def test(self=self): + self.verify("#set $x = 1234\n#assert $x == 999", + ""), + self.failUnlessRaises(AssertionError, test) + + def test3(self): + """simple #assert with WS + """ + self.verify("#set $x = 1234\n #assert $x == 1234 ", + "") + + +class RaiseDirective(OutputTest): + def test1(self): + """simple #raise ValueError + + Should raise ValueError + """ + def test(self=self): + self.verify("#raise ValueError", + ""), + self.failUnlessRaises(ValueError, test) + + def test2(self): + """#raise ValueError in #if block + + Should raise ValueError + """ + def test(self=self): + self.verify("#if 1\n#raise ValueError\n#end if\n", + "") + self.failUnlessRaises(ValueError, test) + + + def test3(self): + """#raise ValueError in #if block + + Shouldn't raise ValueError + """ + self.verify("#if 0\n#raise ValueError\n#else\nblarg#end if\n", + "blarg\n") + + + +class ImportDirective(OutputTest): + def test1(self): + """#import math + """ + self.verify("#import math", + "") + + def test2(self): + """#import math + WS + + Should gobble + """ + self.verify(" #import math ", + "") + + def test3(self): + """#import math + WS + leading text + + Shouldn't gobble + """ + self.verify(" -- #import math ", + " -- ") + + def test4(self): + """#from math import syn + """ + self.verify("#from math import cos", + "") + + def test5(self): + """#from math import cos + WS + Should gobble + """ + self.verify(" #from math import cos ", + "") + + def test6(self): + """#from math import cos + WS + leading text + Shouldn't gobble + """ + self.verify(" -- #from math import cos ", + " -- ") + + def test7(self): + """#from math import cos -- use it + """ + self.verify("#from math import cos\n$cos(0)", + "1.0") + + def test8(self): + """#from math import cos,tan,sin -- and use them + """ + self.verify("#from math import cos, tan, sin\n$cos(0)-$tan(0)-$sin(0)", + "1.0-0.0-0.0") + + def test9(self): + """#import os.path -- use it + """ + + self.verify("#import os.path\n$os.path.exists('.')", + repr(True)) + + def test10(self): + """#import os.path -- use it with NameMapper turned off + """ + self.verify("""## +#compiler-settings +useNameMapper=False +#end compiler-settings +#import os.path +$os.path.exists('.')""", + repr(True)) + + def test11(self): + """#from math import * + """ + + self.verify("#from math import *\n$pow(1,2) $log10(10)", + "1.0 1.0") + +class CompilerDirective(OutputTest): + def test1(self): + """overriding the commentStartToken + """ + self.verify("""$anInt##comment +#compiler commentStartToken = '//' +$anInt//comment +""", + "1\n1\n") + + def test2(self): + """overriding and resetting the commentStartToken + """ + self.verify("""$anInt##comment +#compiler commentStartToken = '//' +$anInt//comment +#compiler reset +$anInt//comment +""", + "1\n1\n1//comment\n") + + +class CompilerSettingsDirective(OutputTest): + + def test1(self): + """overriding the cheetahVarStartToken + """ + self.verify("""$anInt +#compiler-settings +cheetahVarStartToken = @ +#end compiler-settings +@anInt +#compiler-settings reset +$anInt +""", + "1\n1\n1\n") + + def test2(self): + """overriding the directiveStartToken + """ + self.verify("""#set $x = 1234 +$x +#compiler-settings +directiveStartToken = @ +#end compiler-settings +@set $x = 1234 +$x +""", + "1234\n1234\n") + + def test3(self): + """overriding the commentStartToken + """ + self.verify("""$anInt##comment +#compiler-settings +commentStartToken = // +#end compiler-settings +$anInt//comment +""", + "1\n1\n") + +if sys.platform.startswith('java'): + del CompilerDirective + del CompilerSettingsDirective + +class ExtendsDirective(OutputTest): + + def test1(self): + """#extends Cheetah.Templates._SkeletonPage""" + self.verify("""#from Cheetah.Templates._SkeletonPage import _SkeletonPage +#extends _SkeletonPage +#implements respond +$spacer() +""", + '<img src="spacer.gif" width="1" height="1" alt="" />\n') + + + self.verify("""#from Cheetah.Templates._SkeletonPage import _SkeletonPage +#extends _SkeletonPage +#implements respond(foo=1234) +$spacer()$foo +""", + '<img src="spacer.gif" width="1" height="1" alt="" />1234\n') + + def test2(self): + """#extends Cheetah.Templates.SkeletonPage without #import""" + self.verify("""#extends Cheetah.Templates.SkeletonPage +#implements respond +$spacer() +""", + '<img src="spacer.gif" width="1" height="1" alt="" />\n') + + def test3(self): + """#extends Cheetah.Templates.SkeletonPage.SkeletonPage without #import""" + self.verify("""#extends Cheetah.Templates.SkeletonPage.SkeletonPage +#implements respond +$spacer() +""", + '<img src="spacer.gif" width="1" height="1" alt="" />\n') + + def test4(self): + """#extends with globals and searchList test""" + self.verify("""#extends Cheetah.Templates.SkeletonPage +#set global g="Hello" +#implements respond +$g $numOne +""", + 'Hello 1\n') + + +class SuperDirective(OutputTest): + def test1(self): + tmpl1 = Template.compile('''$foo $bar(99) + #def foo: this is base foo + #def bar(arg): super-$arg''') + + tmpl2 = tmpl1.subclass(''' + #implements dummy + #def foo + #super + This is child foo + #super(trans=trans) + $bar(1234) + #end def + #def bar(arg): #super($arg) + ''') + expected = ('this is base foo ' + 'This is child foo\nthis is base foo ' + 'super-1234\n super-99') + assert str(tmpl2()).strip()==expected + + +class ImportantExampleCases(OutputTest): + def test1(self): + """how to make a comma-delimited list""" + self.verify("""#set $sep = '' +#for $letter in $letterList +$sep$letter#slurp +#set $sep = ', ' +#end for +""", + "a, b, c") + +class FilterDirective(OutputTest): + convertEOLs=False + + def _getCompilerSettings(self): + return {'useFilterArgsInPlaceholders':True} + + def test1(self): + """#filter Filter + """ + self.verify("#filter Filter\n$none#end filter", + "") + + self.verify("#filter Filter: $none", + "") + + def test2(self): + """#filter ReplaceNone with WS + """ + self.verify("#filter Filter \n$none#end filter", + "") + + def test3(self): + """#filter MaxLen -- maxlen of 5""" + + self.verify("#filter MaxLen \n${tenDigits, $maxlen=5}#end filter", + "12345") + + def test4(self): + """#filter MaxLen -- no maxlen + """ + self.verify("#filter MaxLen \n${tenDigits}#end filter", + "1234567890") + + def test5(self): + """#filter WebSafe -- basic usage + """ + self.verify("#filter WebSafe \n$webSafeTest#end filter", + "abc <=> &") + + def test6(self): + """#filter WebSafe -- also space + """ + self.verify("#filter WebSafe \n${webSafeTest, $also=' '}#end filter", + "abc <=> &") + + def test7(self): + """#filter WebSafe -- also space, without $ on the args + """ + self.verify("#filter WebSafe \n${webSafeTest, also=' '}#end filter", + "abc <=> &") + + def test8(self): + """#filter Strip -- trailing newline + """ + self.verify("#filter Strip\n$strip1#end filter", + "strippable whitespace\n") + + def test9(self): + """#filter Strip -- no trailing newine + """ + self.verify("#filter Strip\n$strip2#end filter", + "strippable whitespace") + + def test10(self): + """#filter Strip -- multi-line + """ + self.verify("#filter Strip\n$strip3#end filter", + "strippable whitespace\n1 2 3\n") + + def test11(self): + """#filter StripSqueeze -- canonicalize all whitespace to ' ' + """ + self.verify("#filter StripSqueeze\n$strip3#end filter", + "strippable whitespace 1 2 3") + + +class EchoDirective(OutputTest): + def test1(self): + """#echo 1234 + """ + self.verify("#echo 1234", + "1234") + +class SilentDirective(OutputTest): + def test1(self): + """#silent 1234 + """ + self.verify("#silent 1234", + "") + +class ErrorCatcherDirective(OutputTest): + pass + + +class VarExists(OutputTest): # Template.varExists() + + def test1(self): + """$varExists('$anInt') + """ + self.verify("$varExists('$anInt')", + repr(True)) + + def test2(self): + """$varExists('anInt') + """ + self.verify("$varExists('anInt')", + repr(True)) + + def test3(self): + """$varExists('$anInt') + """ + self.verify("$varExists('$bogus')", + repr(False)) + + def test4(self): + """$varExists('$anInt') combined with #if false + """ + self.verify("#if $varExists('$bogus')\n1234\n#else\n999\n#end if", + "999\n") + + def test5(self): + """$varExists('$anInt') combined with #if true + """ + self.verify("#if $varExists('$anInt')\n1234\n#else\n999#end if", + "1234\n") + +class GetVar(OutputTest): # Template.getVar() + def test1(self): + """$getVar('$anInt') + """ + self.verify("$getVar('$anInt')", + "1") + + def test2(self): + """$getVar('anInt') + """ + self.verify("$getVar('anInt')", + "1") + + def test3(self): + """$self.getVar('anInt') + """ + self.verify("$self.getVar('anInt')", + "1") + + def test4(self): + """$getVar('bogus', 1234) + """ + self.verify("$getVar('bogus', 1234)", + "1234") + + def test5(self): + """$getVar('$bogus', 1234) + """ + self.verify("$getVar('$bogus', 1234)", + "1234") + + +class MiscComplexSyntax(OutputTest): + def test1(self): + """Complex use of {},[] and () in a #set expression + ---- + #set $c = {'A':0}[{}.get('a', {'a' : 'A'}['a'])] + $c + """ + self.verify("#set $c = {'A':0}[{}.get('a', {'a' : 'A'}['a'])]\n$c", + "0") + + +class CGI(OutputTest): + """CGI scripts with(out) the CGI environment and with(out) GET variables. + """ + convertEOLs=False + + def _beginCGI(self): + os.environ['REQUEST_METHOD'] = "GET" + def _endCGI(self): + try: + del os.environ['REQUEST_METHOD'] + except KeyError: + pass + _guaranteeNoCGI = _endCGI + + + def test1(self): + """A regular template.""" + self._guaranteeNoCGI() + source = "#extends Cheetah.Tools.CGITemplate\n" + \ + "#implements respond\n" + \ + "$cgiHeaders#slurp\n" + \ + "Hello, world!" + self.verify(source, "Hello, world!") + + + def test2(self): + """A CGI script.""" + self._beginCGI() + source = "#extends Cheetah.Tools.CGITemplate\n" + \ + "#implements respond\n" + \ + "$cgiHeaders#slurp\n" + \ + "Hello, world!" + self.verify(source, "Content-type: text/html\n\nHello, world!") + self._endCGI() + + + def test3(self): + """A (pseudo) Webware servlet. + + This uses the Python syntax escape to set + self._CHEETAH__isControlledByWebKit. + We could instead do '#silent self._CHEETAH__isControlledByWebKit = True', + taking advantage of the fact that it will compile unchanged as long + as there's no '$' in the statement. (It won't compile with an '$' + because that would convert to a function call, and you can't assign + to a function call.) Because this isn't really being called from + Webware, we'd better not use any Webware services! Likewise, we'd + better not call $cgiImport() because it would be misled. + """ + self._beginCGI() + source = "#extends Cheetah.Tools.CGITemplate\n" + \ + "#implements respond\n" + \ + "<% self._CHEETAH__isControlledByWebKit = True %>#slurp\n" + \ + "$cgiHeaders#slurp\n" + \ + "Hello, world!" + self.verify(source, "Hello, world!") + self._endCGI() + + + def test4(self): + """A CGI script with a GET variable.""" + self._beginCGI() + os.environ['QUERY_STRING'] = "cgiWhat=world" + source = "#extends Cheetah.Tools.CGITemplate\n" + \ + "#implements respond\n" + \ + "$cgiHeaders#slurp\n" + \ + "#silent $webInput(['cgiWhat'])##slurp\n" + \ + "Hello, $cgiWhat!" + self.verify(source, + "Content-type: text/html\n\nHello, world!") + del os.environ['QUERY_STRING'] + self._endCGI() + + + +class WhitespaceAfterDirectiveTokens(OutputTest): + def _getCompilerSettings(self): + return {'allowWhitespaceAfterDirectiveStartToken':True} + + def test1(self): + self.verify("# for i in range(10): $i", + "0123456789") + self.verify("# for i in range(10)\n$i# end for", + "0123456789") + self.verify("# for i in range(10)#$i#end for", + "0123456789") + + + +class DefmacroDirective(OutputTest): + def _getCompilerSettings(self): + def aMacro(src): + return '$aStr' + + return {'macroDirectives':{'aMacro':aMacro + }} + + def test1(self): + self.verify("""\ +#defmacro inc: #set @src +=1 +#set i = 1 +#inc: $i +$i""", + "2") + + + + self.verify("""\ +#defmacro test +#for i in range(10): @src +#end defmacro +#test: $i-foo#slurp +#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo012") + + self.verify("""\ +#defmacro test +#for i in range(10): @src +#end defmacro +#test: $i-foo +#for i in range(3): $i""", + "0-foo\n1-foo\n2-foo\n3-foo\n4-foo\n5-foo\n6-foo\n7-foo\n8-foo\n9-foo\n012") + + + self.verify("""\ +#defmacro test: #for i in range(10): @src +#test: $i-foo#slurp +-#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012") + + self.verify("""\ +#defmacro test##for i in range(10): @src#end defmacro##slurp +#test: $i-foo#slurp +-#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012") + + self.verify("""\ +#defmacro testFoo: nothing +#defmacro test(foo=1234): #for i in range(10): @src +#test foo=234: $i-foo#slurp +-#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012") + + self.verify("""\ +#defmacro testFoo: nothing +#defmacro test(foo=1234): #for i in range(10): @src@foo +#test foo='-foo'#$i#end test#-#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012") + + self.verify("""\ +#defmacro testFoo: nothing +#defmacro test(foo=1234): #for i in range(10): @src.strip()@foo +#test foo='-foo': $i +-#for i in range(3): $i""", + "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012") + + def test2(self): + self.verify("#aMacro: foo", + "blarg") + self.verify("#defmacro nested: @macros.aMacro(@src)\n#nested: foo", + "blarg") + + +class Indenter(OutputTest): + convertEOLs=False + + source = """ +public class X +{ + #for $method in $methods + $getMethod($method) + + #end for +} +//end of class + +#def getMethod($method) + #indent ++ + public $getType($method) ${method.Name}($getParams($method.Params)); + #indent -- +#end def + +#def getParams($params) + #indent off + + #for $counter in $range($len($params)) + #if $counter == len($params) - 1 + $params[$counter]#slurp + #else: + $params[$counter], + #end if + #end for + #indent on +#end def + +#def getType($method) + #indent push + #indent=0 + #if $method.Type == "VT_VOID" + void#slurp + #elif $method.Type == "VT_INT" + int#slurp + #elif $method.Type == "VT_VARIANT" + Object#slurp + #end if + #indent pop +#end def +""" + + control = """ +public class X +{ + public void Foo( + _input, + _output); + + + public int Bar( + _str1, + str2, + _str3); + + + public Object Add( + value1, + value); + + +} +//end of class + + + +""" + def _getCompilerSettings(self): + return {'useFilterArgsInPlaceholders':True} + + def searchList(self): # Inside Indenter class. + class Method: + def __init__(self, _name, _type, *_params): + self.Name = _name + self.Type = _type + self.Params = _params + methods = [Method("Foo", "VT_VOID", "_input", "_output"), + Method("Bar", "VT_INT", "_str1", "str2", "_str3"), + Method("Add", "VT_VARIANT", "value1", "value")] + return [{"methods": methods}] + + def test1(self): # Inside Indenter class. + self.verify(self.source, self.control) + + +################################################## +## CREATE CONVERTED EOL VERSIONS OF THE TEST CASES + +if OutputTest._useNewStyleCompilation and versionTuple >= (2,3): + extraCompileKwArgsForDiffBaseclass = {'baseclass':dict} +else: + extraCompileKwArgsForDiffBaseclass = {'baseclass':object} + + +for klass in [var for var in globals().values() + if type(var) == types.ClassType and issubclass(var, unittest.TestCase)]: + name = klass.__name__ + if hasattr(klass,'convertEOLs') and klass.convertEOLs: + win32Src = r"class %(name)s_Win32EOL(%(name)s): _EOLreplacement = '\r\n'"%locals() + macSrc = r"class %(name)s_MacEOL(%(name)s): _EOLreplacement = '\r'"%locals() + #print win32Src + #print macSrc + exec win32Src+'\n' + exec macSrc+'\n' + + if versionTuple >= (2,3): + src = r"class %(name)s_DiffBaseClass(%(name)s): "%locals() + src += " _extraCompileKwArgs = extraCompileKwArgsForDiffBaseclass" + exec src+'\n' + + del name + del klass + +################################################## +## if run from the command line ## + +if __name__ == '__main__': + unittest.main() + +# vim: shiftwidth=4 tabstop=4 expandtab diff --git a/cheetah/Tests/Template.py b/cheetah/Tests/Template.py new file mode 100644 index 0000000..085180d --- /dev/null +++ b/cheetah/Tests/Template.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python + +import pdb +import sys +import types +import os +import os.path +import tempfile +import shutil +import unittest_local_copy as unittest +from Cheetah.Template import Template + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +class TemplateTest(unittest.TestCase): + pass + +class ClassMethods_compile(TemplateTest): + """I am using the same Cheetah source for each test to root out clashes + caused by the compile caching in Template.compile(). + """ + + def test_basicUsage(self): + klass = Template.compile(source='$foo') + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + + def test_baseclassArg(self): + klass = Template.compile(source='$foo', baseclass=dict) + t = klass({'foo':1234}) + assert str(t)=='1234' + + klass2 = Template.compile(source='$foo', baseclass=klass) + t = klass2({'foo':1234}) + assert str(t)=='1234' + + klass3 = Template.compile(source='#implements dummy\n$bar', baseclass=klass2) + t = klass3({'foo':1234}) + assert str(t)=='1234' + + klass4 = Template.compile(source='$foo', baseclass='dict') + t = klass4({'foo':1234}) + assert str(t)=='1234' + + def test_moduleFileCaching(self): + if versionTuple < (2,3): + return + tmpDir = tempfile.mkdtemp() + try: + #print tmpDir + assert os.path.exists(tmpDir) + klass = Template.compile(source='$foo', + cacheModuleFilesForTracebacks=True, + cacheDirForModuleFiles=tmpDir) + mod = sys.modules[klass.__module__] + #print mod.__file__ + assert os.path.exists(mod.__file__) + assert os.path.dirname(mod.__file__)==tmpDir + finally: + shutil.rmtree(tmpDir, True) + + def test_classNameArg(self): + klass = Template.compile(source='$foo', className='foo123') + assert klass.__name__=='foo123' + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + + def test_moduleNameArg(self): + klass = Template.compile(source='$foo', moduleName='foo99') + mod = sys.modules['foo99'] + assert klass.__name__=='foo99' + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + + + klass = Template.compile(source='$foo', + moduleName='foo1', + className='foo2') + mod = sys.modules['foo1'] + assert klass.__name__=='foo2' + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + + + def test_mainMethodNameArg(self): + klass = Template.compile(source='$foo', + className='foo123', + mainMethodName='testMeth') + assert klass.__name__=='foo123' + t = klass(namespaces={'foo':1234}) + #print t.generatedClassCode() + assert str(t)=='1234' + assert t.testMeth()=='1234' + + klass = Template.compile(source='$foo', + moduleName='fooXXX', + className='foo123', + mainMethodName='testMeth', + baseclass=dict) + assert klass.__name__=='foo123' + t = klass({'foo':1234}) + #print t.generatedClassCode() + assert str(t)=='1234' + assert t.testMeth()=='1234' + + + + def test_moduleGlobalsArg(self): + klass = Template.compile(source='$foo', + moduleGlobals={'foo':1234}) + t = klass() + assert str(t)=='1234' + + klass2 = Template.compile(source='$foo', baseclass='Test1', + moduleGlobals={'Test1':dict}) + t = klass2({'foo':1234}) + assert str(t)=='1234' + + klass3 = Template.compile(source='$foo', baseclass='Test1', + moduleGlobals={'Test1':dict, 'foo':1234}) + t = klass3() + assert str(t)=='1234' + + + def test_keepRefToGeneratedCodeArg(self): + klass = Template.compile(source='$foo', + className='unique58', + cacheCompilationResults=False, + keepRefToGeneratedCode=False) + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + assert not t.generatedModuleCode() + + + klass2 = Template.compile(source='$foo', + className='unique58', + keepRefToGeneratedCode=True) + t = klass2(namespaces={'foo':1234}) + assert str(t)=='1234' + assert t.generatedModuleCode() + + klass3 = Template.compile(source='$foo', + className='unique58', + keepRefToGeneratedCode=False) + t = klass3(namespaces={'foo':1234}) + assert str(t)=='1234' + # still there as this class came from the cache + assert t.generatedModuleCode() + + + def test_compilationCache(self): + klass = Template.compile(source='$foo', + className='unique111', + cacheCompilationResults=False) + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + assert not klass._CHEETAH_isInCompilationCache + + + # this time it will place it in the cache + klass = Template.compile(source='$foo', + className='unique111', + cacheCompilationResults=True) + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + assert klass._CHEETAH_isInCompilationCache + + # by default it will be in the cache + klass = Template.compile(source='$foo', + className='unique999099') + t = klass(namespaces={'foo':1234}) + assert str(t)=='1234' + assert klass._CHEETAH_isInCompilationCache + + +class ClassMethods_subclass(TemplateTest): + + def test_basicUsage(self): + klass = Template.compile(source='$foo', baseclass=dict) + t = klass({'foo':1234}) + assert str(t)=='1234' + + klass2 = klass.subclass(source='$foo') + t = klass2({'foo':1234}) + assert str(t)=='1234' + + klass3 = klass2.subclass(source='#implements dummy\n$bar') + t = klass3({'foo':1234}) + assert str(t)=='1234' + + +class Preprocessors(TemplateTest): + + def test_basicUsage1(self): + src='''\ + %set foo = @a + $(@foo*10) + @a''' + src = '\n'.join([ln.strip() for ln in src.splitlines()]) + preprocessors = {'tokens':'@ %', + 'namespaces':{'a':99} + } + klass = Template.compile(src, preprocessors=preprocessors) + assert str(klass())=='990\n99' + + def test_normalizePreprocessorArgVariants(self): + src='%set foo = 12\n%%comment\n$(@foo*10)' + + class Settings1: tokens = '@ %' + Settings1 = Settings1() + + from Cheetah.Template import TemplatePreprocessor + settings = Template._normalizePreprocessorSettings(Settings1) + preprocObj = TemplatePreprocessor(settings) + + def preprocFunc(source, file): + return '$(12*10)', None + + class TemplateSubclass(Template): + pass + + compilerSettings = {'cheetahVarStartToken':'@', + 'directiveStartToken':'%', + 'commentStartToken':'%%', + } + + for arg in ['@ %', + {'tokens':'@ %'}, + {'compilerSettings':compilerSettings}, + {'compilerSettings':compilerSettings, + 'templateInitArgs':{}}, + {'tokens':'@ %', + 'templateAPIClass':TemplateSubclass}, + Settings1, + preprocObj, + preprocFunc, + ]: + + klass = Template.compile(src, preprocessors=arg) + assert str(klass())=='120' + + + def test_complexUsage(self): + src='''\ + %set foo = @a + %def func1: #def func(arg): $arg("***") + %% comment + $(@foo*10) + @func1 + $func(lambda x:c"--$x--@a")''' + src = '\n'.join([ln.strip() for ln in src.splitlines()]) + + + for arg in [{'tokens':'@ %', 'namespaces':{'a':99} }, + {'tokens':'@ %', 'namespaces':{'a':99} }, + ]: + klass = Template.compile(src, preprocessors=arg) + t = klass() + assert str(t)=='990\n--***--99' + + + + def test_i18n(self): + src='''\ + %i18n: This is a $string that needs translation + %i18n id="foo", domain="root": This is a $string that needs translation + ''' + src = '\n'.join([ln.strip() for ln in src.splitlines()]) + klass = Template.compile(src, preprocessors='@ %', baseclass=dict) + t = klass({'string':'bit of text'}) + #print str(t), repr(str(t)) + assert str(t)==('This is a bit of text that needs translation\n'*2)[:-1] + + +class TryExceptImportTest(TemplateTest): + def test_FailCase(self): + ''' Test situation where an inline #import statement will get relocated ''' + source = ''' + #def myFunction() + Ahoy! + #try + #import sys + #except ImportError + $print "This will never happen!" + #end try + #end def + ''' + # This should raise an IndentationError (if the bug exists) + klass = Template.compile(source=source, compilerSettings={'useLegacyImportMode' : False}) + t = klass(namespaces={'foo' : 1234}) + +class ClassMethodSupport(TemplateTest): + def test_BasicDecorator(self): + if sys.version_info[0] == 2 and sys.version_info[1] == 3: + print 'This version of Python doesn\'t support decorators, skipping tests' + return + template = ''' + #@classmethod + #def myClassMethod() + #return '$foo = %s' % $foo + #end def + ''' + template = Template.compile(source=template) + try: + rc = template.myClassMethod(foo='bar') + assert rc == '$foo = bar', (rc, 'Template class method didn\'t return what I expected') + except AttributeError, ex: + self.fail(ex) + +class StaticMethodSupport(TemplateTest): + def test_BasicDecorator(self): + if sys.version_info[0] == 2 and sys.version_info[1] == 3: + print 'This version of Python doesn\'t support decorators, skipping tests' + return + template = ''' + #@staticmethod + #def myStaticMethod() + #return '$foo = %s' % $foo + #end def + ''' + template = Template.compile(source=template) + try: + rc = template.myStaticMethod(foo='bar') + assert rc == '$foo = bar', (rc, 'Template class method didn\'t return what I expected') + except AttributeError, ex: + self.fail(ex) + +class Useless(object): + def boink(self): + return [1, 2, 3] + +class MultipleInheritanceSupport(TemplateTest): + def runTest(self): + template = ''' + #extends Template, Useless + #def foo() + #return [4,5] + $boink() + #end def + ''' + template = Template.compile(template, + moduleGlobals={'Useless' : Useless}, + compilerSettings={'autoImportForExtendsDirective' : False}) + template = template() + result = template.foo() + print result + + +################################################## +## if run from the command line ## + +if __name__ == '__main__': + unittest.main() diff --git a/cheetah/Tests/Test.py b/cheetah/Tests/Test.py new file mode 100755 index 0000000..e168fc9 --- /dev/null +++ b/cheetah/Tests/Test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +''' +Core module of Cheetah's Unit-testing framework + +TODO +================================================================================ +# combo tests +# negative test cases for expected exceptions +# black-box vs clear-box testing +# do some tests that run the Template for long enough to check that the refresh code works +''' + +import sys +import unittest_local_copy as unittest + + + +import SyntaxAndOutput +import NameMapper +import Template +import CheetahWrapper +import Regressions +import Unicode + +suites = [ + unittest.findTestCases(SyntaxAndOutput), + unittest.findTestCases(NameMapper), + unittest.findTestCases(Template), + unittest.findTestCases(Regressions), + unittest.findTestCases(Unicode), +] + +if not sys.platform.startswith('java'): + suites.append(unittest.findTestCases(CheetahWrapper)) + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + if 'xml' in sys.argv: + import xmlrunner + runner = xmlrunner.XMLTestRunner(filename='Cheetah-Tests.xml') + + results = runner.run(unittest.TestSuite(suites)) + + + + + diff --git a/cheetah/Tests/Unicode.py b/cheetah/Tests/Unicode.py new file mode 100644 index 0000000..da59bed --- /dev/null +++ b/cheetah/Tests/Unicode.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- encoding: utf8 -*- + +from Cheetah.Template import Template +from Cheetah import CheetahWrapper +from Cheetah import DummyTransaction +import imp +import os +import pdb +import random +import sys +import tempfile +import unittest_local_copy as unittest # This is stupid + +class CommandLineTest(unittest.TestCase): + def createAndCompile(self, source): + sourcefile = '-' + while sourcefile.find('-') != -1: + sourcefile = tempfile.mktemp() + + fd = open('%s.tmpl' % sourcefile, 'w') + fd.write(source) + fd.close() + + wrap = CheetahWrapper.CheetahWrapper() + wrap.main(['cheetah', 'compile', '--nobackup', sourcefile]) + module_path, module_name = os.path.split(sourcefile) + module = loadModule(module_name, [module_path]) + template = getattr(module, module_name) + return template + +class JBQ_UTF8_Test1(unittest.TestCase): + def runTest(self): + t = Template.compile(source="""Main file with |$v| + + $other""") + + otherT = Template.compile(source="Other template with |$v|") + other = otherT() + t.other = other + + t.v = u'Unicode String' + t.other.v = u'Unicode String' + + assert unicode(t()) + +class JBQ_UTF8_Test2(unittest.TestCase): + def runTest(self): + t = Template.compile(source="""Main file with |$v| + + $other""") + + otherT = Template.compile(source="Other template with |$v|") + other = otherT() + t.other = other + + t.v = u'Unicode String with eacute é' + t.other.v = u'Unicode String' + + assert unicode(t()) + + +class JBQ_UTF8_Test3(unittest.TestCase): + def runTest(self): + t = Template.compile(source="""Main file with |$v| + + $other""") + + otherT = Template.compile(source="Other template with |$v|") + other = otherT() + t.other = other + + t.v = u'Unicode String with eacute é' + t.other.v = u'Unicode String and an eacute é' + + assert unicode(t()) + +class JBQ_UTF8_Test4(unittest.TestCase): + def runTest(self): + t = Template.compile(source="""#encoding utf-8 + Main file with |$v| and eacute in the template é""") + + t.v = 'Unicode String' + + assert unicode(t()) + +class JBQ_UTF8_Test5(unittest.TestCase): + def runTest(self): + t = Template.compile(source="""#encoding utf-8 + Main file with |$v| and eacute in the template é""") + + t.v = u'Unicode String' + + assert unicode(t()) + +def loadModule(moduleName, path=None): + if path: + assert isinstance(path, list) + try: + mod = sys.modules[moduleName] + except KeyError: + fp = None + + try: + fp, pathname, description = imp.find_module(moduleName, path) + mod = imp.load_module(moduleName, fp, pathname, description) + finally: + if fp: + fp.close() + return mod + +class JBQ_UTF8_Test6(unittest.TestCase): + def runTest(self): + source = """#encoding utf-8 + #set $someUnicodeString = u"Bébé" + Main file with |$v| and eacute in the template é""" + t = Template.compile(source=source) + + t.v = u'Unicode String' + + assert unicode(t()) + +class JBQ_UTF8_Test7(CommandLineTest): + def runTest(self): + source = """#encoding utf-8 + #set $someUnicodeString = u"Bébé" + Main file with |$v| and eacute in the template é""" + + template = self.createAndCompile(source) + template.v = u'Unicode String' + + assert unicode(template()) + +class JBQ_UTF8_Test8(CommandLineTest): + def testStaticCompile(self): + source = """#encoding utf-8 +#set $someUnicodeString = u"Bébé" +$someUnicodeString""" + + template = self.createAndCompile(source)() + + a = unicode(template).encode("utf-8") + self.assertEquals("Bébé", a) + + def testDynamicCompile(self): + source = """#encoding utf-8 +#set $someUnicodeString = u"Bébé" +$someUnicodeString""" + + template = Template(source = source) + + a = unicode(template).encode("utf-8") + self.assertEquals("Bébé", a) + +class Unicode_in_SearchList_Test(CommandLineTest): + def test_BasicASCII(self): + source = '''This is $adjective''' + + template = self.createAndCompile(source) + assert template and issubclass(template, Template) + template = template(searchList=[{'adjective' : u'neat'}]) + assert template.respond() + + def test_Thai(self): + # The string is something in Thai + source = '''This is $foo $adjective''' + template = self.createAndCompile(source) + assert template and issubclass(template, Template) + template = template(searchList=[{'foo' : 'bar', + 'adjective' : u'\u0e22\u0e34\u0e19\u0e14\u0e35\u0e15\u0e49\u0e2d\u0e19\u0e23\u0e31\u0e1a'}]) + assert template.respond() + + def test_ErrorReporting(self): + utf8 = '\xe0\xb8\xa2\xe0\xb8\xb4\xe0\xb8\x99\xe0\xb8\x94\xe0\xb8\xb5\xe0\xb8\x95\xe0\xb9\x89\xe0\xb8\xad\xe0\xb8\x99\xe0\xb8\xa3\xe0\xb8\xb1\xe0\xb8\x9a' + + source = '''This is $adjective''' + template = self.createAndCompile(source) + assert template and issubclass(template, Template) + template = template(searchList=[{'adjective' : utf8}]) + self.failUnlessRaises(DummyTransaction.DummyResponseFailure, template.respond) + + + +if __name__ == '__main__': + unittest.main() diff --git a/cheetah/Tests/__init__.py b/cheetah/Tests/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/cheetah/Tests/__init__.py @@ -0,0 +1 @@ +# diff --git a/cheetah/Tests/unittest_local_copy.py b/cheetah/Tests/unittest_local_copy.py new file mode 100755 index 0000000..a5f5499 --- /dev/null +++ b/cheetah/Tests/unittest_local_copy.py @@ -0,0 +1,978 @@ +#!/usr/bin/env python +""" This is a hacked version of PyUnit that extends its reporting capabilities +with optional meta data on the test cases. It also makes it possible to +separate the standard and error output streams in TextTestRunner. + +It's a hack rather than a set of subclasses because a) Steve had used double +underscore private attributes for some things I needed access to, and b) the +changes affected so many classes that it was easier just to hack it. + +The changes are in the following places: +TestCase: + - minor refactoring of __init__ and __call__ internals + - added some attributes and methods for storing and retrieving meta data + +_TextTestResult + - refactored the stream handling + - incorporated all the output code from TextTestRunner + - made the output of FAIL and ERROR information more flexible and + incorporated the new meta data from TestCase + - added a flag called 'explain' to __init__ that controls whether the new ' + explanation' meta data from TestCase is printed along with tracebacks + +TextTestRunner + - delegated all output to _TextTestResult + - added 'err' and 'explain' to the __init__ signature to match the changes + in _TextTestResult + +TestProgram + - added -e and --explain as flags on the command line + +-- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001) + +- _TestTextResult.printErrorList(): print blank line after each traceback + +-- Mike Orr <mso@oz.net> (Nov 11, 2002) + +TestCase methods copied from unittest in Python 2.3: + - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places. + - .failIfAlmostEqual(first, second, places=7, msg=None) + +-- Mike Orr (Jan 5, 2004) + + +Below is the original docstring for unittest. +--------------------------------------------------------------------------- +Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's +Smalltalk testing framework. + +This module contains the core framework classes that form the basis of +specific test cases and suites (TestCase, TestSuite etc.), and also a +text-based utility class for running the tests and reporting the results +(TextTestRunner). + +Simple usage: + + import unittest + + class IntegerArithmenticTestCase(unittest.TestCase): + def testAdd(self): ## test method names begin 'test*' + self.assertEquals((1 + 2), 3) + self.assertEquals(0 + 1, 1) + def testMultiply(self); + self.assertEquals((0 * 10), 0) + self.assertEquals((5 * 8), 40) + + if __name__ == '__main__': + unittest.main() + +Further information is available in the bundled documentation, and from + + http://pyunit.sourceforge.net/ + +Copyright (c) 1999, 2000, 2001 Steve Purcell +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__author__ = "Steve Purcell" +__email__ = "stephen_purcell at yahoo dot com" +__revision__ = "$Revision: 1.11 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import os +import re +import string +import sys +import time +import traceback +import types +import pprint + +################################################## +## CONSTANTS & GLOBALS + +try: + True,False +except NameError: + True, False = (1==1),(1==0) + +############################################################################## +# Test framework core +############################################################################## + + +class TestResult: + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is a + tuple of values as returned by sys.exc_info(). + """ + def __init__(self): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = 0 + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun = self.testsRun + 1 + + def stopTest(self, test): + "Called when the given test has been run" + pass + + def addError(self, test, err): + "Called when an error has occurred" + self.errors.append((test, err)) + + def addFailure(self, test, err): + "Called when a failure has occurred" + self.failures.append((test, err)) + + def addSuccess(self, test): + "Called when a test has completed successfully" + pass + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return len(self.failures) == len(self.errors) == 0 + + def stop(self): + "Indicates that the tests should be aborted" + self.shouldStop = 1 + + def __repr__(self): + return "<%s run=%i errors=%i failures=%i>" % \ + (self.__class__, self.testsRun, len(self.errors), + len(self.failures)) + +class TestCase: + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + """ + + # This attribute determines which exception will be raised when + # the instance's assertion methods fail; test methods raising this + # exception will be deemed to have 'failed' rather than 'errored' + + failureException = AssertionError + + # the name of the fixture. Used for displaying meta data about the test + name = None + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + self._testMethodName = methodName + self._setupTestMethod() + self._setupMetaData() + + def _setupTestMethod(self): + try: + self._testMethod = getattr(self, self._testMethodName) + except AttributeError: + raise ValueError, "no such test method in %s: %s" % \ + (self.__class__, self._testMethodName) + + ## meta data methods + + def _setupMetaData(self): + """Setup the default meta data for the test case: + + - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName + - description: 1st line of Class docstring + 1st line of method docstring + - explanation: rest of Class docstring + rest of method docstring + + """ + + + testDoc = self._testMethod.__doc__ or '\n' + testDocLines = testDoc.splitlines() + + testDescription = testDocLines[0].strip() + if len(testDocLines) > 1: + testExplanation = '\n'.join( + [ln.strip() for ln in testDocLines[1:]] + ).strip() + else: + testExplanation = '' + + fixtureDoc = self.__doc__ or '\n' + fixtureDocLines = fixtureDoc.splitlines() + fixtureDescription = fixtureDocLines[0].strip() + if len(fixtureDocLines) > 1: + fixtureExplanation = '\n'.join( + [ln.strip() for ln in fixtureDocLines[1:]] + ).strip() + else: + fixtureExplanation = '' + + if not self.name: + self.name = self.__class__ + self._id = "%s.%s" % (self.name, self._testMethodName) + + if not fixtureDescription: + self._description = testDescription + else: + self._description = fixtureDescription + ', ' + testDescription + + if not fixtureExplanation: + self._explanation = testExplanation + else: + self._explanation = ['Fixture Explanation:', + '--------------------', + fixtureExplanation, + '', + 'Test Explanation:', + '-----------------', + testExplanation + ] + self._explanation = '\n'.join(self._explanation) + + def id(self): + return self._id + + def setId(self, id): + self._id = id + + def describe(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + return self._description + + shortDescription = describe + + def setDescription(self, descr): + self._description = descr + + def explain(self): + return self._explanation + + def setExplanation(self, expln): + self._explanation = expln + + ## core methods + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + pass + + def run(self, result=None): + return self(result) + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + pass + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + self._testMethod() + self.tearDown() + + ## internal methods + + def defaultTestResult(self): + return TestResult() + + def __call__(self, result=None): + if result is None: + result = self.defaultTestResult() + + result.startTest(self) + try: + try: + self.setUp() + except: + result.addError(self, self.__exc_info()) + return + + ok = 0 + try: + self._testMethod() + ok = 1 + except self.failureException, e: + result.addFailure(self, self.__exc_info()) + except: + result.addError(self, self.__exc_info()) + try: + self.tearDown() + except: + result.addError(self, self.__exc_info()) + ok = 0 + if ok: + result.addSuccess(self) + finally: + result.stopTest(self) + + return result + + def countTestCases(self): + return 1 + + def __str__(self): + return "%s (%s)" % (self._testMethodName, self.__class__) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (self.__class__, self._testMethodName) + + def __exc_info(self): + """Return a version of sys.exc_info() with the traceback frame + minimised; usually the top level of the traceback frame is not + needed. + """ + exctype, excvalue, tb = sys.exc_info() + if sys.platform[:4] == 'java': ## tracebacks look different in Jython + return (exctype, excvalue, tb) + newtb = tb.tb_next + if newtb is None: + return (exctype, excvalue, tb) + return (exctype, excvalue, newtb) + + ## methods for use by the test cases + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException, msg + + def failIf(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: raise self.failureException, msg + + def failUnless(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: raise self.failureException, msg + + def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): + """Fail unless an exception of class excClass is thrown + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + thrown, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + """ + try: + apply(callableObj, args, kwargs) + except excClass: + return + else: + if hasattr(excClass,'__name__'): excName = excClass.__name__ + else: excName = str(excClass) + raise self.failureException, excName + + def failUnlessEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '!=' + operator. + """ + if first != second: + raise self.failureException, (msg or '%s != %s' % (first, second)) + + def failIfEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' + operator. + """ + if first == second: + raise self.failureException, (msg or '%s == %s' % (first, second)) + + def failUnlessAlmostEqual(self, first, second, places=7, msg=None): + """Fail if the two objects are unequal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero. + + Note that decimal places (from zero) is usually not the same + as significant digits (measured from the most signficant digit). + """ + if round(second-first, places) != 0: + raise self.failureException, \ + (msg or '%s != %s within %s places' % (`first`, `second`, `places` )) + + def failIfAlmostEqual(self, first, second, places=7, msg=None): + """Fail if the two objects are equal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero. + + Note that decimal places (from zero) is usually not the same + as significant digits (measured from the most signficant digit). + """ + if round(second-first, places) == 0: + raise self.failureException, \ + (msg or '%s == %s within %s places' % (`first`, `second`, `places`)) + + ## aliases + + assertEqual = assertEquals = failUnlessEqual + + assertNotEqual = assertNotEquals = failIfEqual + + assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual + + assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual + + assertRaises = failUnlessRaises + + assert_ = failUnless + + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + PyUnit framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, + description=None): + TestCase.__init__(self) + self.__setUpFunc = setUp + self.__tearDownFunc = tearDown + self.__testFunc = testFunc + self.__description = description + + def setUp(self): + if self.__setUpFunc is not None: + self.__setUpFunc() + + def tearDown(self): + if self.__tearDownFunc is not None: + self.__tearDownFunc() + + def runTest(self): + self.__testFunc() + + def id(self): + return self.__testFunc.__name__ + + def __str__(self): + return "%s (%s)" % (self.__class__, self.__testFunc.__name__) + + def __repr__(self): + return "<%s testFunc=%s>" % (self.__class__, self.__testFunc) + + + def describe(self): + if self.__description is not None: return self.__description + doc = self.__testFunc.__doc__ + return doc and string.strip(string.split(doc, "\n")[0]) or None + + ## aliases + shortDescription = describe + +class TestSuite: + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + def __init__(self, tests=(), suiteName=None): + self._tests = [] + self._testMap = {} + self.suiteName = suiteName + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (self.__class__, pprint.pformat(self._tests)) + + __str__ = __repr__ + + def countTestCases(self): + cases = 0 + for test in self._tests: + cases = cases + test.countTestCases() + return cases + + def addTest(self, test): + self._tests.append(test) + if isinstance(test, TestSuite) and test.suiteName: + name = test.suiteName + elif isinstance(test, TestCase): + #print test, test._testMethodName + name = test._testMethodName + else: + name = test.__class__.__name__ + self._testMap[name] = test + + def addTests(self, tests): + for test in tests: + self.addTest(test) + + def getTestForName(self, name): + return self._testMap[name] + + def run(self, result): + return self(result) + + def __call__(self, result): + for test in self._tests: + if result.shouldStop: + break + test(result) + return result + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self._tests: test.debug() + + +############################################################################## +# Text UI +############################################################################## + +class StreamWrapper: + def __init__(self, out=sys.stdout, err=sys.stderr): + self._streamOut = out + self._streamErr = err + + def write(self, txt): + self._streamOut.write(txt) + self._streamOut.flush() + + def writeln(self, *lines): + for line in lines: + self.write(line + '\n') + if not lines: + self.write('\n') + + def writeErr(self, txt): + self._streamErr.write(txt) + + def writelnErr(self, *lines): + for line in lines: + self.writeErr(line + '\n') + if not lines: + self.writeErr('\n') + + +class _TextTestResult(TestResult, StreamWrapper): + _separatorWidth = 70 + _sep1 = '=' + _sep2 = '-' + _errorSep1 = '*' + _errorSep2 = '-' + _errorSep3 = '' + + def __init__(self, + stream=sys.stdout, + errStream=sys.stderr, + verbosity=1, + explain=False): + + TestResult.__init__(self) + StreamWrapper.__init__(self, out=stream, err=errStream) + + self._verbosity = verbosity + self._showAll = verbosity > 1 + self._dots = (verbosity == 1) + self._explain = explain + + ## startup and shutdown methods + + def beginTests(self): + self._startTime = time.time() + + def endTests(self): + self._stopTime = time.time() + self._timeTaken = float(self._stopTime - self._startTime) + + def stop(self): + self.shouldStop = 1 + + ## methods called for each test + + def startTest(self, test): + TestResult.startTest(self, test) + if self._showAll: + self.write("%s (%s)" %( test.id(), test.describe() ) ) + self.write(" ... ") + + def addSuccess(self, test): + TestResult.addSuccess(self, test) + if self._showAll: + self.writeln("ok") + elif self._dots: + self.write('.') + + def addError(self, test, err): + TestResult.addError(self, test, err) + if self._showAll: + self.writeln("ERROR") + elif self._dots: + self.write('E') + if err[0] is KeyboardInterrupt: + self.stop() + + def addFailure(self, test, err): + TestResult.addFailure(self, test, err) + if self._showAll: + self.writeln("FAIL") + elif self._dots: + self.write('F') + + ## display methods + + def summarize(self): + self.printErrors() + self.writeSep2() + run = self.testsRun + self.writeln("Ran %d test%s in %.3fs" % + (run, run == 1 and "" or "s", self._timeTaken)) + self.writeln() + if not self.wasSuccessful(): + self.writeErr("FAILED (") + failed, errored = map(len, (self.failures, self.errors)) + if failed: + self.writeErr("failures=%d" % failed) + if errored: + if failed: self.writeErr(", ") + self.writeErr("errors=%d" % errored) + self.writelnErr(")") + else: + self.writelnErr("OK") + + def writeSep1(self): + self.writeln(self._sep1 * self._separatorWidth) + + def writeSep2(self): + self.writeln(self._sep2 * self._separatorWidth) + + def writeErrSep1(self): + self.writeln(self._errorSep1 * self._separatorWidth) + + def writeErrSep2(self): + self.writeln(self._errorSep2 * self._separatorWidth) + + def printErrors(self): + if self._dots or self._showAll: + self.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.writeErrSep1() + self.writelnErr("%s %s (%s)" % (flavour, test.id(), test.describe() )) + if self._explain: + expln = test.explain() + if expln: + self.writeErrSep2() + self.writeErr( expln ) + self.writelnErr() + + self.writeErrSep2() + for line in apply(traceback.format_exception, err): + for l in line.split("\n")[:-1]: + self.writelnErr(l) + self.writelnErr("") + +class TextTestRunner: + def __init__(self, + stream=sys.stdout, + errStream=sys.stderr, + verbosity=1, + explain=False): + + self._out = stream + self._err = errStream + self._verbosity = verbosity + self._explain = explain + + ## main methods + + def run(self, test): + result = self._makeResult() + result.beginTests() + test( result ) + result.endTests() + result.summarize() + + return result + + ## internal methods + + def _makeResult(self): + return _TextTestResult(stream=self._out, + errStream=self._err, + verbosity=self._verbosity, + explain=self._explain, + ) + +############################################################################## +# Locating and loading tests +############################################################################## + +class TestLoader: + """This class is responsible for loading tests according to various + criteria and returning them wrapped in a Test + """ + testMethodPrefix = 'test' + sortTestMethodsUsing = cmp + suiteClass = TestSuite + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all tests cases contained in testCaseClass""" + return self.suiteClass(tests=map(testCaseClass, + self.getTestCaseNames(testCaseClass)), + suiteName=testCaseClass.__name__) + + def loadTestsFromModule(self, module): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if type(obj) == types.ClassType and issubclass(obj, TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + return self.suiteClass(tests) + + def loadTestsFromName(self, name, module=None): + """Return a suite of all tests cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = string.split(name, '.') + if module is None: + if not parts: + raise ValueError, "incomplete test name: %s" % name + else: + parts_copy = parts[:] + while parts_copy: + try: + module = __import__(string.join(parts_copy,'.')) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: raise + parts = parts[1:] + obj = module + for part in parts: + if isinstance(obj, TestSuite): + obj = obj.getTestForName(part) + else: + obj = getattr(obj, part) + + if type(obj) == types.ModuleType: + return self.loadTestsFromModule(obj) + elif type(obj) == types.ClassType and issubclass(obj, TestCase): + return self.loadTestsFromTestCase(obj) + elif type(obj) == types.UnboundMethodType: + return obj.im_class(obj.__name__) + elif isinstance(obj, TestSuite): + return obj + elif isinstance(obj, TestCase): + return obj + elif callable(obj): + test = obj() + if not isinstance(test, TestCase) and \ + not isinstance(test, TestSuite): + raise ValueError, \ + "calling %s returned %s, not a test" %(obj,test) + return test + else: + raise ValueError, "don't know how to make test from: %s" % obj + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all tests cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [] + for name in names: + suites.append(self.loadTestsFromName(name, module)) + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass. + """ + testFnNames = [fn for fn in dir(testCaseClass) if fn.startswith(self.testMethodPrefix)] + if hasattr(testCaseClass, 'runTest'): + testFnNames.append('runTest') + for baseclass in testCaseClass.__bases__: + for testFnName in self.getTestCaseNames(baseclass): + if testFnName not in testFnNames: # handle overridden methods + testFnNames.append(testFnName) + if self.sortTestMethodsUsing: + testFnNames.sort(self.sortTestMethodsUsing) + return testFnNames + + + +defaultTestLoader = TestLoader() + + +############################################################################## +# Patches for old functions: these functions should be considered obsolete +############################################################################## + +def _makeLoader(prefix, sortUsing, suiteClass=None): + loader = TestLoader() + loader.sortTestMethodsUsing = sortUsing + loader.testMethodPrefix = prefix + if suiteClass: loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): + return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) + +def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) + +############################################################################## +# Facilities for running tests from the command line +############################################################################## + +class TestProgram: + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + USAGE = """\ +Usage: %(progName)s [options] [test] [...] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output + -e, --expain Output extra test details if there is a failure or error + +Examples: + %(progName)s - run default set of tests + %(progName)s MyTestSuite - run suite 'MyTestSuite' + %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite' + %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething + %(progName)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + def __init__(self, module='__main__', defaultTest=None, + argv=None, testRunner=None, testLoader=defaultTestLoader, + testSuite=None): + if type(module) == type(''): + self.module = __import__(module) + for part in string.split(module,'.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + self.test = testSuite + self.verbosity = 1 + self.explain = 0 + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: print msg + print self.USAGE % self.__dict__ + sys.exit(2) + + def parseArgs(self, argv): + import getopt + try: + options, args = getopt.getopt(argv[1:], 'hHvqer', + ['help','verbose','quiet','explain', 'raise']) + for opt, value in options: + if opt in ('-h','-H','--help'): + self.usageExit() + if opt in ('-q','--quiet'): + self.verbosity = 0 + if opt in ('-v','--verbose'): + self.verbosity = 2 + if opt in ('-e','--explain'): + self.explain = True + if len(args) == 0 and self.defaultTest is None and self.test is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + return + if len(args) > 0: + self.testNames = args + else: + self.testNames = (self.defaultTest,) + self.createTests() + except getopt.error, msg: + self.usageExit(msg) + + def createTests(self): + if self.test == None: + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def runTests(self): + if self.testRunner is None: + self.testRunner = TextTestRunner(verbosity=self.verbosity, + explain=self.explain) + result = self.testRunner.run(self.test) + self._cleanupAfterRunningTests() + sys.exit(not result.wasSuccessful()) + + def _cleanupAfterRunningTests(self): + """A hook method that is called immediately prior to calling + sys.exit(not result.wasSuccessful()) in self.runTests(). + """ + pass + +main = TestProgram + + +############################################################################## +# Executing this module from the command line +############################################################################## + +if __name__ == "__main__": + main(module=None) + +# vim: shiftwidth=4 tabstop=4 expandtab diff --git a/cheetah/Tests/xmlrunner.py b/cheetah/Tests/xmlrunner.py new file mode 100644 index 0000000..dc49c56 --- /dev/null +++ b/cheetah/Tests/xmlrunner.py @@ -0,0 +1,381 @@ +""" +XML Test Runner for PyUnit +""" + +# Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in +# the Public Domain. With contributions by Paolo Borelli. + +__revision__ = "$Id: /private/python/stdlib/xmlrunner.py 16654 2007-11-12T12:46:35.368945Z srittau $" + +import os.path +import re +import sys +import time +import traceback +import unittest +from StringIO import StringIO +from xml.sax.saxutils import escape + +from StringIO import StringIO + + + +class _TestInfo(object): + + """Information about a particular test. + + Used by _XMLTestResult. + + """ + + def __init__(self, test, time): + _pieces = test.id().split('.') + (self._class, self._method) = ('.'.join(_pieces[:-1]), _pieces[-1]) + self._time = time + self._error = None + self._failure = None + + + def print_report(self, stream): + """Print information about this test case in XML format to the + supplied stream. + + """ + stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ + { + "class": self._class, + "method": self._method, + "time": self._time, + }) + if self._failure != None: + self._print_error(stream, 'failure', self._failure) + if self._error != None: + self._print_error(stream, 'error', self._error) + stream.write('</testcase>\n') + + def _print_error(self, stream, tagname, error): + """Print information from a failure or error to the supplied stream.""" + text = escape(str(error[1])) + stream.write('\n') + stream.write(' <%s type="%s">%s\n' \ + % (tagname, issubclass(error[0], Exception) and error[0].__name__ or str(error[0]), text)) + tb_stream = StringIO() + traceback.print_tb(error[2], None, tb_stream) + stream.write(escape(tb_stream.getvalue())) + stream.write(' </%s>\n' % tagname) + stream.write(' ') + +# Module level functions since Python 2.3 doesn't grok decorators +def create_success(test, time): + """Create a _TestInfo instance for a successful test.""" + return _TestInfo(test, time) + +def create_failure(test, time, failure): + """Create a _TestInfo instance for a failed test.""" + info = _TestInfo(test, time) + info._failure = failure + return info + +def create_error(test, time, error): + """Create a _TestInfo instance for an erroneous test.""" + info = _TestInfo(test, time) + info._error = error + return info + +class _XMLTestResult(unittest.TestResult): + + """A test result class that stores result as XML. + + Used by XMLTestRunner. + + """ + + def __init__(self, classname): + unittest.TestResult.__init__(self) + self._test_name = classname + self._start_time = None + self._tests = [] + self._error = None + self._failure = None + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + self._error = None + self._failure = None + self._start_time = time.time() + + def stopTest(self, test): + time_taken = time.time() - self._start_time + unittest.TestResult.stopTest(self, test) + if self._error: + info = create_error(test, time_taken, self._error) + elif self._failure: + info = create_failure(test, time_taken, self._failure) + else: + info = create_success(test, time_taken) + self._tests.append(info) + + def addError(self, test, err): + unittest.TestResult.addError(self, test, err) + self._error = err + + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + self._failure = err + + def print_report(self, stream, time_taken, out, err): + """Prints the XML report to the supplied stream. + + The time the tests took to perform as well as the captured standard + output and standard error streams must be passed in.a + + """ + stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ + { "e": len(self.errors), "f": len(self.failures) }) + stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ + { + "n": self._test_name, + "t": self.testsRun, + "time": time_taken, + }) + for info in self._tests: + info.print_report(stream) + stream.write(' <system-out><![CDATA[%s]]></system-out>\n' % out) + stream.write(' <system-err><![CDATA[%s]]></system-err>\n' % err) + stream.write('</testsuite>\n') + + +class XMLTestRunner(object): + + """A test runner that stores results in XML format compatible with JUnit. + + XMLTestRunner(stream=None) -> XML test runner + + The XML file is written to the supplied stream. If stream is None, the + results are stored in a file called TEST-<module>.<class>.xml in the + current working directory (if not overridden with the path property), + where <module> and <class> are the module and class name of the test class. + + """ + + def __init__(self, *args, **kwargs): + self._stream = kwargs.get('stream') + self._filename = kwargs.get('filename') + self._path = "." + + def run(self, test): + """Run the given test case or test suite.""" + class_ = test.__class__ + classname = class_.__module__ + "." + class_.__name__ + if self._stream == None: + filename = "TEST-%s.xml" % classname + if self._filename: + filename = self._filename + stream = file(os.path.join(self._path, filename), "w") + stream.write('<?xml version="1.0" encoding="utf-8"?>\n') + else: + stream = self._stream + + result = _XMLTestResult(classname) + start_time = time.time() + + # TODO: Python 2.5: Use the with statement + old_stdout = sys.stdout + old_stderr = sys.stderr + sys.stdout = StringIO() + sys.stderr = StringIO() + + try: + test(result) + try: + out_s = sys.stdout.getvalue() + except AttributeError: + out_s = "" + try: + err_s = sys.stderr.getvalue() + except AttributeError: + err_s = "" + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + time_taken = time.time() - start_time + result.print_report(stream, time_taken, out_s, err_s) + if self._stream == None: + stream.close() + + return result + + def _set_path(self, path): + self._path = path + + path = property(lambda self: self._path, _set_path, None, + """The path where the XML files are stored. + + This property is ignored when the XML file is written to a file + stream.""") + + +class XMLTestRunnerTest(unittest.TestCase): + def setUp(self): + self._stream = StringIO() + + def _try_test_run(self, test_class, expected): + + """Run the test suite against the supplied test class and compare the + XML result against the expected XML string. Fail if the expected + string doesn't match the actual string. All time attribute in the + expected string should have the value "0.000". All error and failure + messages are reduced to "Foobar". + + """ + + runner = XMLTestRunner(self._stream) + runner.run(unittest.makeSuite(test_class)) + + got = self._stream.getvalue() + # Replace all time="X.YYY" attributes by time="0.000" to enable a + # simple string comparison. + got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got) + # Likewise, replace all failure and error messages by a simple "Foobar" + # string. + got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got) + got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got) + + self.assertEqual(expected, got) + + def test_no_tests(self): + """Regression test: Check whether a test run without any tests + matches a previous run. + + """ + class TestTest(unittest.TestCase): + pass + self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000"> + <system-out><![CDATA[]]></system-out> + <system-err><![CDATA[]]></system-err> +</testsuite> +""") + + def test_success(self): + """Regression test: Check whether a test run with a successful test + matches a previous run. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + pass + self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> + <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> + <system-out><![CDATA[]]></system-out> + <system-err><![CDATA[]]></system-err> +</testsuite> +""") + + def test_failure(self): + """Regression test: Check whether a test run with a failing test + matches a previous run. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + self.assert_(False) + self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000"> + <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> + <failure type="exceptions.AssertionError">Foobar</failure> + </testcase> + <system-out><![CDATA[]]></system-out> + <system-err><![CDATA[]]></system-err> +</testsuite> +""") + + def test_error(self): + """Regression test: Check whether a test run with a erroneous test + matches a previous run. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + raise IndexError() + self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> + <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> + <error type="exceptions.IndexError">Foobar</error> + </testcase> + <system-out><![CDATA[]]></system-out> + <system-err><![CDATA[]]></system-err> +</testsuite> +""") + + def test_stdout_capture(self): + """Regression test: Check whether a test run with output to stdout + matches a previous run. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + print "Test" + self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> + <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> + <system-out><![CDATA[Test +]]></system-out> + <system-err><![CDATA[]]></system-err> +</testsuite> +""") + + def test_stderr_capture(self): + """Regression test: Check whether a test run with output to stderr + matches a previous run. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + print >>sys.stderr, "Test" + self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> + <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> + <system-out><![CDATA[]]></system-out> + <system-err><![CDATA[Test +]]></system-err> +</testsuite> +""") + + class NullStream(object): + """A file-like object that discards everything written to it.""" + def write(self, buffer): + pass + + def test_unittests_changing_stdout(self): + """Check whether the XMLTestRunner recovers gracefully from unit tests + that change stdout, but don't change it back properly. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + sys.stdout = XMLTestRunnerTest.NullStream() + + runner = XMLTestRunner(self._stream) + runner.run(unittest.makeSuite(TestTest)) + + def test_unittests_changing_stderr(self): + """Check whether the XMLTestRunner recovers gracefully from unit tests + that change stderr, but don't change it back properly. + + """ + class TestTest(unittest.TestCase): + def test_foo(self): + sys.stderr = XMLTestRunnerTest.NullStream() + + runner = XMLTestRunner(self._stream) + runner.run(unittest.makeSuite(TestTest)) + + +class XMLTestProgram(unittest.TestProgram): + def runTests(self): + if self.testRunner is None: + self.testRunner = XMLTestRunner() + unittest.TestProgram.runTests(self) + +main = XMLTestProgram + + +if __name__ == "__main__": + main(module=None) |