2 # $Id: CheetahWrapper.py,v 1.5 2006/01/07 07:18:44 tavis_rudd Exp $
3 """Tests for the 'cheetah' command.
5 Besides unittest usage, recognizes the following command-line options:
6 --list CheetahWrapper.py
7 List all scenarios that are tested. The argument is the path
10 Don't delete scratch directory at end.
12 Show the output of each subcommand. (Normally suppressed.)
15 ================================================================================
16 Author: Mike Orr <iron@mso.oz.net>,
17 Version: $Revision: 1.5 $
18 Start Date: 2001/10/01
19 Last Revision Date: $Date: 2006/01/07 07:18:44 $
21 __author__
= "Mike Orr <iron@mso.oz.net>"
22 __revision__
= "$Revision: 1.5 $"[11:-2]
25 ##################################################
28 import commands
, os
, shutil
, sys
, tempfile
29 import unittest_local_copy
as unittest
31 import re
# Used by listTests.
32 from Cheetah
.CheetahWrapper
import CheetahWrapper
# Used by NoBackup.
33 from Cheetah
.Utils
.optik
import OptionParser
# Used by main.
35 ##################################################
36 ## CONSTANTS & GLOBALS ##
41 True, False = (1==1),(1==0)
43 DELETE
= True # True to clean up after ourselves, False for debugging.
44 OUTPUT
= False # Normally False, True for debugging.
46 #DELETE = False # True to clean up after ourselves, False for debugging.
47 #OUTPUT = True # Normally False, True for debugging.
49 BACKUP_SUFFIX
= CheetahWrapper
.BACKUP_SUFFIX
52 sys
.stderr
.write(msg
+ '\n')
54 ##################################################
57 class CFBase(unittest
.TestCase
):
58 """Base class for "cheetah compile" and "cheetah fill" unit tests.
60 srcDir
= '' # Nonblank to create source directory.
61 subdirs
= ('child', 'child/grandkid') # Delete in reverse order.
62 srcFiles
= ('a.tmpl', 'child/a.tmpl', 'child/grandkid/a.tmpl')
63 expectError
= False # Used by --list option.
65 def inform(self
, message
):
70 """Create the top-level directories, subdirectories and .tmpl
74 # Step 1: Create the scratch directory and chdir into it.
75 self
.scratchDir
= scratchDir
= tempfile
.mktemp()
77 self
.origCwd
= os
.getcwd()
81 # Step 2: Create source subdirectories.
82 for dir in self
.subdirs
:
84 # Step 3: Create the .tmpl files, each in its proper directory.
85 for fil
in self
.srcFiles
:
87 f
.write("Hello, world!\n")
92 os
.chdir(self
.origCwd
)
94 shutil
.rmtree(self
.scratchDir
, True) # Ignore errors.
95 if os
.path
.exists(self
.scratchDir
):
96 warn("Warning: unable to delete scratch directory %s")
98 warn("Warning: not deleting scratch directory %s" % self
.scratchDir
)
101 def _checkDestFileHelper(self
, path
, expected
,
102 allowSurroundingText
, errmsg
):
103 """Low-level helper to check a destination file.
105 in : path, string, the destination path.
106 expected, string, the expected contents.
107 allowSurroundingtext, bool, allow the result to contain
108 additional text around the 'expected' substring?
109 errmsg, string, the error message. It may contain the
110 following "%"-operator keys: path, expected, result.
113 path
= os
.path
.abspath(path
)
114 exists
= os
.path
.exists(path
)
115 msg
= "destination file missing: %s" % path
116 self
.failUnless(exists
, msg
)
120 if allowSurroundingText
:
121 success
= result
.find(expected
) != -1
123 success
= result
== expected
124 msg
= errmsg
% locals()
125 self
.failUnless(success
, msg
)
128 def checkCompile(self
, path
):
129 # Raw string to prevent "\n" from being converted to a newline.
130 #expected = R"write('Hello, world!\n')"
131 expected
= R
"'Hello, world!\n')" # might output a u'' string
133 destination file %(path)s doesn't contain expected substring:
135 self
._checkDestFileHelper
(path
, expected
, True, errmsg
)
138 def checkFill(self
, path
):
139 expected
= "Hello, world!\n"
141 destination file %(path)s contains wrong result.
142 Expected %(expected)r
144 self
._checkDestFileHelper
(path
, expected
, False, errmsg
)
147 def checkSubdirPyInit(self
, path
):
148 """Verify a destination subdirectory exists and contains an
151 exists
= os
.path
.exists(path
)
152 msg
= "destination subdirectory %s misssing" % path
153 self
.failUnless(exists
, msg
)
154 initPath
= os
.path
.join(path
, "__init__.py")
155 exists
= os
.path
.exists(initPath
)
156 msg
= "destination init file missing: %s" % initPath
157 self
.failUnless(exists
, msg
)
160 def checkNoBackup(self
, path
):
161 """Verify 'path' does not exist. (To check --nobackup.)
163 exists
= os
.path
.exists(path
)
164 msg
= "backup file exists in spite of --nobackup: %s" % path
165 self
.failIf(exists
, msg
)
168 def go(self
, cmd
, expectedStatus
=0, expectedOutputSubstring
=None):
169 """Run a "cheetah compile" or "cheetah fill" subcommand.
171 in : cmd, string, the command to run.
172 expectedStatus, int, subcommand's expected output status.
173 0 if the subcommand is expected to succeed, 1-255 otherwise.
174 expectedOutputSubstring, string, substring which much appear
175 in the standard output or standard error. None to skip this
179 # Use commands.getstatusoutput instead of os.system so
180 # that we can mimic ">/dev/null 2>/dev/null" even on
181 # non-Unix platforms.
182 exit
, output
= commands
.getstatusoutput(cmd
)
183 status
, signal
= divmod(exit
, 256)
185 if output
.endswith("\n"):
188 print "SUBCOMMAND:", cmd
191 msg
= "subcommand killed by signal %d: %s" % (signal
, cmd
)
192 self
.failUnlessEqual(signal
, 0, msg
)
193 msg
= "subcommand exit status %d: %s" % (status
, cmd
)
194 if status
!=expectedStatus
:
196 self
.failUnlessEqual(status
, expectedStatus
, msg
)
197 if expectedOutputSubstring
is not None:
198 msg
= "substring %r not found in subcommand output: %s" % \
199 (expectedOutputSubstring
, cmd
)
200 substringTest
= output
.find(expectedOutputSubstring
) != -1
201 self
.failUnless(substringTest
, msg
)
204 def goExpectError(self
, cmd
):
205 """Run a subcommand and expect it to fail.
207 in : cmd, string, the command to run.
210 # Use commands.getstatusoutput instead of os.system so
211 # that we can mimic ">/dev/null 2>/dev/null" even on
212 # non-Unix platforms.
213 exit
, output
= commands
.getstatusoutput(cmd
)
214 status
, signal
= divmod(exit
, 256)
215 msg
= "subcommand killed by signal %s: %s" % (signal
, cmd
)
216 self
.failUnlessEqual(signal
, 0, msg
) # Signal must be 0.
217 msg
= "subcommand exit status %s: %s" % (status
, cmd
)
218 self
.failIfEqual(status
, 0, msg
) # Status must *not* be 0.
220 if output
.endswith("\n"):
223 print "SUBCOMMAND:", cmd
228 class CFIdirBase(CFBase
):
229 """Subclass for tests with --idir.
232 subdirs
= ('SRC/child', 'SRC/child/grandkid') # Delete in reverse order.
233 srcFiles
= ('SRC/a.tmpl', 'SRC/child/a.tmpl', 'SRC/child/grandkid/a.tmpl')
237 ##################################################
240 class OneFile(CFBase
):
241 def testCompile(self
):
242 self
.go("cheetah compile a.tmpl")
243 self
.checkCompile("a.py")
246 self
.go("cheetah fill a.tmpl")
247 self
.checkFill("a.html")
250 self
.go("cheetah fill --oext txt a.tmpl")
251 self
.checkFill("a.txt")
254 class OneFileNoExtension(CFBase
):
255 def testCompile(self
):
256 self
.go("cheetah compile a")
257 self
.checkCompile("a.py")
260 self
.go("cheetah fill a")
261 self
.checkFill("a.html")
264 self
.go("cheetah fill --oext txt a")
265 self
.checkFill("a.txt")
268 class SplatTmpl(CFBase
):
269 def testCompile(self
):
270 self
.go("cheetah compile *.tmpl")
271 self
.checkCompile("a.py")
274 self
.go("cheetah fill *.tmpl")
275 self
.checkFill("a.html")
278 self
.go("cheetah fill --oext txt *.tmpl")
279 self
.checkFill("a.txt")
281 class ThreeFilesWithSubdirectories(CFBase
):
282 def testCompile(self
):
283 self
.go("cheetah compile a.tmpl child/a.tmpl child/grandkid/a.tmpl")
284 self
.checkCompile("a.py")
285 self
.checkCompile("child/a.py")
286 self
.checkCompile("child/grandkid/a.py")
289 self
.go("cheetah fill a.tmpl child/a.tmpl child/grandkid/a.tmpl")
290 self
.checkFill("a.html")
291 self
.checkFill("child/a.html")
292 self
.checkFill("child/grandkid/a.html")
295 self
.go("cheetah fill --oext txt a.tmpl child/a.tmpl child/grandkid/a.tmpl")
296 self
.checkFill("a.txt")
297 self
.checkFill("child/a.txt")
298 self
.checkFill("child/grandkid/a.txt")
301 class ThreeFilesWithSubdirectoriesNoExtension(CFBase
):
302 def testCompile(self
):
303 self
.go("cheetah compile a child/a child/grandkid/a")
304 self
.checkCompile("a.py")
305 self
.checkCompile("child/a.py")
306 self
.checkCompile("child/grandkid/a.py")
309 self
.go("cheetah fill a child/a child/grandkid/a")
310 self
.checkFill("a.html")
311 self
.checkFill("child/a.html")
312 self
.checkFill("child/grandkid/a.html")
315 self
.go("cheetah fill --oext txt a child/a child/grandkid/a")
316 self
.checkFill("a.txt")
317 self
.checkFill("child/a.txt")
318 self
.checkFill("child/grandkid/a.txt")
321 class SplatTmplWithSubdirectories(CFBase
):
322 def testCompile(self
):
323 self
.go("cheetah compile *.tmpl child/*.tmpl child/grandkid/*.tmpl")
324 self
.checkCompile("a.py")
325 self
.checkCompile("child/a.py")
326 self
.checkCompile("child/grandkid/a.py")
329 self
.go("cheetah fill *.tmpl child/*.tmpl child/grandkid/*.tmpl")
330 self
.checkFill("a.html")
331 self
.checkFill("child/a.html")
332 self
.checkFill("child/grandkid/a.html")
335 self
.go("cheetah fill --oext txt *.tmpl child/*.tmpl child/grandkid/*.tmpl")
336 self
.checkFill("a.txt")
337 self
.checkFill("child/a.txt")
338 self
.checkFill("child/grandkid/a.txt")
341 class OneFileWithOdir(CFBase
):
342 def testCompile(self
):
343 self
.go("cheetah compile --odir DEST a.tmpl")
344 self
.checkSubdirPyInit("DEST")
345 self
.checkCompile("DEST/a.py")
348 self
.go("cheetah fill --odir DEST a.tmpl")
349 self
.checkFill("DEST/a.html")
352 self
.go("cheetah fill --odir DEST --oext txt a.tmpl")
353 self
.checkFill("DEST/a.txt")
356 class VarietyWithOdir(CFBase
):
357 def testCompile(self
):
358 self
.go("cheetah compile --odir DEST a.tmpl child/a child/grandkid/*.tmpl")
359 self
.checkSubdirPyInit("DEST")
360 self
.checkSubdirPyInit("DEST/child")
361 self
.checkSubdirPyInit("DEST/child/grandkid")
362 self
.checkCompile("DEST/a.py")
363 self
.checkCompile("DEST/child/a.py")
364 self
.checkCompile("DEST/child/grandkid/a.py")
367 self
.go("cheetah fill --odir DEST a.tmpl child/a child/grandkid/*.tmpl")
368 self
.checkFill("DEST/a.html")
369 self
.checkFill("DEST/child/a.html")
370 self
.checkFill("DEST/child/grandkid/a.html")
373 self
.go("cheetah fill --odir DEST --oext txt a.tmpl child/a child/grandkid/*.tmpl")
374 self
.checkFill("DEST/a.txt")
375 self
.checkFill("DEST/child/a.txt")
376 self
.checkFill("DEST/child/grandkid/a.txt")
379 class RecurseExplicit(CFBase
):
380 def testCompile(self
):
381 self
.go("cheetah compile -R child")
382 self
.checkCompile("child/a.py")
383 self
.checkCompile("child/grandkid/a.py")
386 self
.go("cheetah fill -R child")
387 self
.checkFill("child/a.html")
388 self
.checkFill("child/grandkid/a.html")
391 self
.go("cheetah fill -R --oext txt child")
392 self
.checkFill("child/a.txt")
393 self
.checkFill("child/grandkid/a.txt")
396 class RecurseImplicit(CFBase
):
397 def testCompile(self
):
398 self
.go("cheetah compile -R")
399 self
.checkCompile("child/a.py")
400 self
.checkCompile("child/grandkid/a.py")
403 self
.go("cheetah fill -R")
404 self
.checkFill("a.html")
405 self
.checkFill("child/a.html")
406 self
.checkFill("child/grandkid/a.html")
409 self
.go("cheetah fill -R --oext txt")
410 self
.checkFill("a.txt")
411 self
.checkFill("child/a.txt")
412 self
.checkFill("child/grandkid/a.txt")
415 class RecurseExplicitWIthOdir(CFBase
):
416 def testCompile(self
):
417 self
.go("cheetah compile -R --odir DEST child")
418 self
.checkSubdirPyInit("DEST/child")
419 self
.checkSubdirPyInit("DEST/child/grandkid")
420 self
.checkCompile("DEST/child/a.py")
421 self
.checkCompile("DEST/child/grandkid/a.py")
424 self
.go("cheetah fill -R --odir DEST child")
425 self
.checkFill("DEST/child/a.html")
426 self
.checkFill("DEST/child/grandkid/a.html")
429 self
.go("cheetah fill -R --odir DEST --oext txt child")
430 self
.checkFill("DEST/child/a.txt")
431 self
.checkFill("DEST/child/grandkid/a.txt")
435 def testCompile(self
):
436 self
.go("cheetah compile --flat child/a.tmpl")
437 self
.checkCompile("a.py")
440 self
.go("cheetah fill --flat child/a.tmpl")
441 self
.checkFill("a.html")
444 self
.go("cheetah fill --flat --oext txt child/a.tmpl")
445 self
.checkFill("a.txt")
448 class FlatRecurseCollision(CFBase
):
451 def testCompile(self
):
452 self
.goExpectError("cheetah compile -R --flat")
455 self
.goExpectError("cheetah fill -R --flat")
458 self
.goExpectError("cheetah fill -R --flat")
461 class IdirRecurse(CFIdirBase
):
462 def testCompile(self
):
463 self
.go("cheetah compile -R --idir SRC child")
464 self
.checkSubdirPyInit("child")
465 self
.checkSubdirPyInit("child/grandkid")
466 self
.checkCompile("child/a.py")
467 self
.checkCompile("child/grandkid/a.py")
470 self
.go("cheetah fill -R --idir SRC child")
471 self
.checkFill("child/a.html")
472 self
.checkFill("child/grandkid/a.html")
475 self
.go("cheetah fill -R --idir SRC --oext txt child")
476 self
.checkFill("child/a.txt")
477 self
.checkFill("child/grandkid/a.txt")
480 class IdirOdirRecurse(CFIdirBase
):
481 def testCompile(self
):
482 self
.go("cheetah compile -R --idir SRC --odir DEST child")
483 self
.checkSubdirPyInit("DEST/child")
484 self
.checkSubdirPyInit("DEST/child/grandkid")
485 self
.checkCompile("DEST/child/a.py")
486 self
.checkCompile("DEST/child/grandkid/a.py")
489 self
.go("cheetah fill -R --idir SRC --odir DEST child")
490 self
.checkFill("DEST/child/a.html")
491 self
.checkFill("DEST/child/grandkid/a.html")
494 self
.go("cheetah fill -R --idir SRC --odir DEST --oext txt child")
495 self
.checkFill("DEST/child/a.txt")
496 self
.checkFill("DEST/child/grandkid/a.txt")
499 class IdirFlatRecurseCollision(CFIdirBase
):
502 def testCompile(self
):
503 self
.goExpectError("cheetah compile -R --flat --idir SRC")
506 self
.goExpectError("cheetah fill -R --flat --idir SRC")
509 self
.goExpectError("cheetah fill -R --flat --idir SRC --oext txt")
512 class NoBackup(CFBase
):
513 """Run the command twice each time and verify a backup file is
516 def testCompile(self
):
517 self
.go("cheetah compile --nobackup a.tmpl")
518 self
.go("cheetah compile --nobackup a.tmpl")
519 self
.checkNoBackup("a.py" + BACKUP_SUFFIX
)
522 self
.go("cheetah fill --nobackup a.tmpl")
523 self
.go("cheetah fill --nobackup a.tmpl")
524 self
.checkNoBackup("a.html" + BACKUP_SUFFIX
)
527 self
.go("cheetah fill --nobackup --oext txt a.tmpl")
528 self
.go("cheetah fill --nobackup --oext txt a.tmpl")
529 self
.checkNoBackup("a.txt" + BACKUP_SUFFIX
)
535 ##################################################
538 def listTests(cheetahWrapperFile
):
539 """cheetahWrapperFile, string, path of this script.
541 XXX TODO: don't print test where expectError is true.
543 rx
= re
.compile( R
'self\.go\("(.*?)"\)' )
544 f
= open(cheetahWrapperFile
)
554 ##################################################
557 class MyOptionParser(OptionParser
):
558 """Disable the standard --help and --verbose options since
559 --help is used for another purpose.
561 standard_option_list
= []
564 global DELETE
, OUTPUT
565 parser
= MyOptionParser()
566 parser
.add_option("--list", action
="store", dest
="listTests")
567 parser
.add_option("--nodelete", action
="store_true")
568 parser
.add_option("--output", action
="store_true")
569 # The following options are passed to unittest.
570 parser
.add_option("-e", "--explain", action
="store_true")
571 parser
.add_option("-h", "--help", action
="store_true")
572 parser
.add_option("-v", "--verbose", action
="store_true")
573 parser
.add_option("-q", "--quiet", action
="store_true")
574 opts
, files
= parser
.parse_args()
580 listTests(opts
.listTests
)
582 # Eliminate script-specific command-line arguments to prevent
583 # errors in unittest.
585 for opt
in ("explain", "help", "verbose", "quiet"):
586 if getattr(opts
, opt
):
587 sys
.argv
.append("--" + opt
)
588 sys
.argv
.extend(files
)
591 ##################################################
592 ## if run from the command line ##
594 if __name__
== '__main__': main()
596 # vim: sw=4 ts=4 expandtab