diff options
Diffstat (limited to 'cobbler/Cheetah/Tests')
-rw-r--r-- | cobbler/Cheetah/Tests/CheetahWrapper.py | 596 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/FileRefresh.py | 55 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/NameMapper.py | 539 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/SyntaxAndOutput.py | 3170 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/Template.py | 312 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/Test.py | 70 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/__init__.py | 1 | ||||
-rw-r--r-- | cobbler/Cheetah/Tests/unittest_local_copy.py | 977 |
8 files changed, 5720 insertions, 0 deletions
diff --git a/cobbler/Cheetah/Tests/CheetahWrapper.py b/cobbler/Cheetah/Tests/CheetahWrapper.py new file mode 100644 index 0000000..316c43b --- /dev/null +++ b/cobbler/Cheetah/Tests/CheetahWrapper.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python +# $Id: CheetahWrapper.py,v 1.5 2006/01/07 07:18:44 tavis_rudd Exp $ +"""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.) + +Meta-Data +================================================================================ +Author: Mike Orr <iron@mso.oz.net>, +Version: $Revision: 1.5 $ +Start Date: 2001/10/01 +Last Revision Date: $Date: 2006/01/07 07:18:44 $ +""" +__author__ = "Mike Orr <iron@mso.oz.net>" +__revision__ = "$Revision: 1.5 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import commands, os, shutil, sys, tempfile +import unittest_local_copy as unittest + +import re # Used by listTests. +from Cheetah.CheetahWrapper import CheetahWrapper # Used by NoBackup. +from Cheetah.Utils.optik import OptionParser # Used by main. + +################################################## +## CONSTANTS & GLOBALS ## + +try: + True,False +except NameError: + True, False = (1==1),(1==0) + +DELETE = True # True to clean up after ourselves, False for debugging. +OUTPUT = False # Normally False, True for debugging. + +#DELETE = False # True to clean up after ourselves, False for debugging. +#OUTPUT = True # Normally False, True for debugging. + +BACKUP_SUFFIX = CheetahWrapper.BACKUP_SUFFIX + +def warn(msg): + sys.stderr.write(msg + '\n') + +################################################## +## TEST BASE CLASSES + +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 = R"'Hello, world!\n')" # 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 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. + """ + # Use commands.getstatusoutput instead of os.system so + # that we can mimic ">/dev/null 2>/dev/null" even on + # non-Unix platforms. + exit, output = commands.getstatusoutput(cmd) + status, signal = divmod(exit, 256) + if OUTPUT: + if output.endswith("\n"): + output = output[:-1] + print + print "SUBCOMMAND:", cmd + print output + print + msg = "subcommand killed by signal %d: %s" % (signal, cmd) + self.failUnlessEqual(signal, 0, msg) + msg = "subcommand exit status %d: %s" % (status, cmd) + if status!=expectedStatus: + print output + self.failUnlessEqual(status, expectedStatus, msg) + 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) + + + def goExpectError(self, cmd): + """Run a subcommand and expect it to fail. + + in : cmd, string, the command to run. + out: None. + """ + # Use commands.getstatusoutput instead of os.system so + # that we can mimic ">/dev/null 2>/dev/null" even on + # non-Unix platforms. + exit, output = commands.getstatusoutput(cmd) + status, signal = divmod(exit, 256) + msg = "subcommand killed by signal %s: %s" % (signal, cmd) + self.failUnlessEqual(signal, 0, msg) # Signal must be 0. + msg = "subcommand exit status %s: %s" % (status, cmd) + self.failIfEqual(status, 0, msg) # Status must *not* be 0. + if OUTPUT: + if output.endswith("\n"): + output = output[:-1] + print + print "SUBCOMMAND:", cmd + print output + print + + +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.goExpectError("cheetah compile -R --flat") + + def testFill(self): + self.goExpectError("cheetah fill -R --flat") + + def testText(self): + self.goExpectError("cheetah fill -R --flat") + + +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.goExpectError("cheetah compile -R --flat --idir SRC") + + def testFill(self): + self.goExpectError("cheetah fill -R --flat --idir SRC") + + def testText(self): + self.goExpectError("cheetah fill -R --flat --idir SRC --oext txt") + + +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) + + + + + +################################################## +## LIST TESTS ## + +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() + +################################################## +## MAIN ROUTINE ## + +class MyOptionParser(OptionParser): + """Disable the standard --help and --verbose options since + --help is used for another purpose. + """ + standard_option_list = [] + +def main(): + global DELETE, OUTPUT + parser = MyOptionParser() + 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("-h", "--help", 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", "help", "verbose", "quiet"): + if getattr(opts, opt): + sys.argv.append("--" + opt) + sys.argv.extend(files) + unittest.main() + +################################################## +## if run from the command line ## + +if __name__ == '__main__': main() + +# vim: sw=4 ts=4 expandtab diff --git a/cobbler/Cheetah/Tests/FileRefresh.py b/cobbler/Cheetah/Tests/FileRefresh.py new file mode 100644 index 0000000..4beb3e7 --- /dev/null +++ b/cobbler/Cheetah/Tests/FileRefresh.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# $Id: FileRefresh.py,v 1.6 2002/10/01 17:52:03 tavis_rudd Exp $ +"""Tests to make sure that the file-update-monitoring code is working properly + +THIS TEST MODULE IS JUST A SHELL AT THE MOMENT. Feel like filling it in?? + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com>, +Version: $Revision: 1.6 $ +Start Date: 2001/10/01 +Last Revision Date: $Date: 2002/10/01 17:52:03 $ +""" +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.6 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import sys +import types +import os +import os.path + + +import unittest_local_copy as unittest +from Cheetah.Template import Template + +################################################## +## CONSTANTS & GLOBALS ## + +try: + True,False +except NameError: + True, False = (1==1),(1==0) + +################################################## +## TEST DATA FOR USE IN THE TEMPLATES ## + +################################################## +## TEST BASE CLASSES + +class TemplateTest(unittest.TestCase): + pass + +################################################## +## TEST CASE CLASSES + + +################################################## +## if run from the command line ## + +if __name__ == '__main__': + unittest.main() diff --git a/cobbler/Cheetah/Tests/NameMapper.py b/cobbler/Cheetah/Tests/NameMapper.py new file mode 100644 index 0000000..2782463 --- /dev/null +++ b/cobbler/Cheetah/Tests/NameMapper.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python +# $Id: NameMapper.py,v 1.11 2006/01/15 20:45:22 tavis_rudd Exp $ +"""NameMapper Tests + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com>, +Version: $Revision: 1.11 $ +Start Date: 2001/10/01 +Last Revision Date: $Date: 2006/01/15 20:45:22 $ +""" +from __future__ import generators +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.11 $"[11:-2] +import sys +import types +import os +import os.path + +import unittest_local_copy as unittest +from Cheetah.NameMapper import NotFound, valueForKey, \ + valueForName, valueFromSearchList, valueFromFrame, valueFromFrameOrSearchList + + +################################################## +## TEST DATA FOR USE IN THE TEMPLATES ## + +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/cobbler/Cheetah/Tests/SyntaxAndOutput.py b/cobbler/Cheetah/Tests/SyntaxAndOutput.py new file mode 100644 index 0000000..09abc4d --- /dev/null +++ b/cobbler/Cheetah/Tests/SyntaxAndOutput.py @@ -0,0 +1,3170 @@ +#!/usr/bin/env python +# $Id: SyntaxAndOutput.py,v 1.105 2006/06/21 23:48:19 tavis_rudd Exp $ +"""Syntax and Output tests. + +TODO +- #finally +- #filter +- #errorCatcher +- #echo +- #silent + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com> +Version: $Revision: 1.105 $ +Start Date: 2001/03/30 +Last Revision Date: $Date: 2006/06/21 23:48:19 $ +""" +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.105 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import sys +import types +import re +from copy import deepcopy +import os +import os.path +import new +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: pass +################################################## +## CONSTANTS & GLOBALS ## + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +try: + True,False +except NameError: + True, False = (1==1),(1==0) + +################################################## +## TEST DATA FOR USE IN THE TEMPLATES ## + +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: + try: + output = templateObj.respond() # rather than __str__, because of unicode + if outputEncoding: + output = output.decode(outputEncoding) + assert output==expectedOutput, self._outputMismatchReport(output, expectedOutput) + except: + #print >>sys.stderr, moduleCode + raise + 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 ascii\n\xe1\x88\xb4", + "\xe1\x88\xb4") + + def test5(self): + """basic #encoding """ + self.verify("#encoding latin-1\nAndr\202", + u'Andr\202', outputEncoding='latin-1') + +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) + + +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") + + +class DecoratorDirective(OutputTest): + def test1(self): + """single line #def with decorator""" + 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') + +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 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 ReplaceNone + """ + self.verify("#filter ReplaceNone\n$none#end filter", + "") + + self.verify("#filter ReplaceNone: $none", + "") + + def test2(self): + """#filter ReplaceNone with WS + """ + self.verify("#filter ReplaceNone \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/cobbler/Cheetah/Tests/Template.py b/cobbler/Cheetah/Tests/Template.py new file mode 100644 index 0000000..b97cf5d --- /dev/null +++ b/cobbler/Cheetah/Tests/Template.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python +# $Id: Template.py,v 1.16 2006/02/03 21:05:50 tavis_rudd Exp $ +"""Tests of the Template class API + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com>, +Version: $Revision: 1.16 $ +Start Date: 2001/10/01 +Last Revision Date: $Date: 2006/02/03 21:05:50 $ +""" +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.16 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import sys +import types +import os +import os.path +import tempfile +import shutil +import unittest_local_copy as unittest +from Cheetah.Template import Template + +################################################## +## CONSTANTS & GLOBALS ## + +majorVer, minorVer = sys.version_info[0], sys.version_info[1] +versionTuple = (majorVer, minorVer) + +try: + True,False +except NameError: + True, False = (1==1),(1==0) + +################################################## +## TEST DATA FOR USE IN THE TEMPLATES ## + +################################################## +## TEST BASE CLASSES + +class TemplateTest(unittest.TestCase): + pass + +################################################## +## TEST CASE CLASSES + +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] + + +################################################## +## if run from the command line ## + +if __name__ == '__main__': + unittest.main() diff --git a/cobbler/Cheetah/Tests/Test.py b/cobbler/Cheetah/Tests/Test.py new file mode 100644 index 0000000..9e46a5d --- /dev/null +++ b/cobbler/Cheetah/Tests/Test.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# $Id: Test.py,v 1.44 2006/01/15 20:45:10 tavis_rudd Exp $ +"""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 + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com>, +License: This software is released for unlimited distribution under the + terms of the MIT license. See the LICENSE file. +Version: $Revision: 1.44 $ +Start Date: 2001/03/30 +Last Revision Date: $Date: 2006/01/15 20:45:10 $ +""" +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.44 $"[11:-2] + + +################################################## +## DEPENDENCIES ## + +import sys +import unittest_local_copy as unittest + +################################################## +## CONSTANTS & GLOBALS + +try: + True, False +except NameError: + True, False = (1==1),(1==0) + +################################################## +## TESTS + +import SyntaxAndOutput +import NameMapper +import Template +import FileRefresh +import CheetahWrapper + +SyntaxSuite = unittest.findTestCases(SyntaxAndOutput) +NameMapperSuite = unittest.findTestCases(NameMapper) +TemplateSuite = unittest.findTestCases(Template) +FileRefreshSuite = unittest.findTestCases(FileRefresh) +if not sys.platform.startswith('java'): + CheetahWrapperSuite = unittest.findTestCases(CheetahWrapper) + +from SyntaxAndOutput import * +from NameMapper import * +from Template import * +from FileRefresh import * + +if not sys.platform.startswith('java'): + from CheetahWrapper import * + +################################################## +## if run from the command line + +if __name__ == '__main__': + unittest.main() + + + diff --git a/cobbler/Cheetah/Tests/__init__.py b/cobbler/Cheetah/Tests/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/cobbler/Cheetah/Tests/__init__.py @@ -0,0 +1 @@ +# diff --git a/cobbler/Cheetah/Tests/unittest_local_copy.py b/cobbler/Cheetah/Tests/unittest_local_copy.py new file mode 100644 index 0000000..54061ae --- /dev/null +++ b/cobbler/Cheetah/Tests/unittest_local_copy.py @@ -0,0 +1,977 @@ +#!/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 = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p, + dir(testCaseClass)) + 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 |