Merge branch 'master' into subfolders-8.3
[pyTivo.git] / Cheetah / Tests / CheetahWrapper.py
blob316c43ba88a51ac733c0c9c53f58edf347ded01e
1 #!/usr/bin/env python
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
8 of this script.
9 --nodelete
10 Don't delete scratch directory at end.
11 --output
12 Show the output of each subcommand. (Normally suppressed.)
14 Meta-Data
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 $
20 """
21 __author__ = "Mike Orr <iron@mso.oz.net>"
22 __revision__ = "$Revision: 1.5 $"[11:-2]
25 ##################################################
26 ## DEPENDENCIES ##
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 ##
38 try:
39 True,False
40 except NameError:
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
51 def warn(msg):
52 sys.stderr.write(msg + '\n')
54 ##################################################
55 ## TEST BASE CLASSES
57 class CFBase(unittest.TestCase):
58 """Base class for "cheetah compile" and "cheetah fill" unit tests.
59 """
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):
66 if self.verbose:
67 print message
69 def setUp(self):
70 """Create the top-level directories, subdirectories and .tmpl
71 files.
72 """
73 I = self.inform
74 # Step 1: Create the scratch directory and chdir into it.
75 self.scratchDir = scratchDir = tempfile.mktemp()
76 os.mkdir(scratchDir)
77 self.origCwd = os.getcwd()
78 os.chdir(scratchDir)
79 if self.srcDir:
80 os.mkdir(self.srcDir)
81 # Step 2: Create source subdirectories.
82 for dir in self.subdirs:
83 os.mkdir(dir)
84 # Step 3: Create the .tmpl files, each in its proper directory.
85 for fil in self.srcFiles:
86 f = open(fil, 'w')
87 f.write("Hello, world!\n")
88 f.close()
91 def tearDown(self):
92 os.chdir(self.origCwd)
93 if DELETE:
94 shutil.rmtree(self.scratchDir, True) # Ignore errors.
95 if os.path.exists(self.scratchDir):
96 warn("Warning: unable to delete scratch directory %s")
97 else:
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.
111 out: None
113 path = os.path.abspath(path)
114 exists = os.path.exists(path)
115 msg = "destination file missing: %s" % path
116 self.failUnless(exists, msg)
117 f = open(path, 'r')
118 result = f.read()
119 f.close()
120 if allowSurroundingText:
121 success = result.find(expected) != -1
122 else:
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
132 errmsg = """\
133 destination file %(path)s doesn't contain expected substring:
134 %(expected)r"""
135 self._checkDestFileHelper(path, expected, True, errmsg)
138 def checkFill(self, path):
139 expected = "Hello, world!\n"
140 errmsg = """\
141 destination file %(path)s contains wrong result.
142 Expected %(expected)r
143 Found %(result)r"""
144 self._checkDestFileHelper(path, expected, False, errmsg)
147 def checkSubdirPyInit(self, path):
148 """Verify a destination subdirectory exists and contains an
149 __init__.py file.
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
176 test.
177 out: None.
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)
184 if OUTPUT:
185 if output.endswith("\n"):
186 output = output[:-1]
187 print
188 print "SUBCOMMAND:", cmd
189 print output
190 print
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:
195 print output
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.
208 out: None.
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.
219 if OUTPUT:
220 if output.endswith("\n"):
221 output = output[:-1]
222 print
223 print "SUBCOMMAND:", cmd
224 print output
225 print
228 class CFIdirBase(CFBase):
229 """Subclass for tests with --idir.
231 srcDir = 'SRC'
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 ##################################################
238 ## TEST CASE CLASSES
240 class OneFile(CFBase):
241 def testCompile(self):
242 self.go("cheetah compile a.tmpl")
243 self.checkCompile("a.py")
245 def testFill(self):
246 self.go("cheetah fill a.tmpl")
247 self.checkFill("a.html")
249 def testText(self):
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")
259 def testFill(self):
260 self.go("cheetah fill a")
261 self.checkFill("a.html")
263 def testText(self):
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")
273 def testFill(self):
274 self.go("cheetah fill *.tmpl")
275 self.checkFill("a.html")
277 def testText(self):
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")
288 def testFill(self):
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")
294 def testText(self):
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")
308 def testFill(self):
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")
314 def testText(self):
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")
328 def testFill(self):
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")
334 def testText(self):
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")
347 def testFill(self):
348 self.go("cheetah fill --odir DEST a.tmpl")
349 self.checkFill("DEST/a.html")
351 def testText(self):
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")
366 def testFill(self):
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")
372 def testText(self):
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")
385 def testFill(self):
386 self.go("cheetah fill -R child")
387 self.checkFill("child/a.html")
388 self.checkFill("child/grandkid/a.html")
390 def testText(self):
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")
402 def testFill(self):
403 self.go("cheetah fill -R")
404 self.checkFill("a.html")
405 self.checkFill("child/a.html")
406 self.checkFill("child/grandkid/a.html")
408 def testText(self):
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")
423 def testFill(self):
424 self.go("cheetah fill -R --odir DEST child")
425 self.checkFill("DEST/child/a.html")
426 self.checkFill("DEST/child/grandkid/a.html")
428 def testText(self):
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")
434 class Flat(CFBase):
435 def testCompile(self):
436 self.go("cheetah compile --flat child/a.tmpl")
437 self.checkCompile("a.py")
439 def testFill(self):
440 self.go("cheetah fill --flat child/a.tmpl")
441 self.checkFill("a.html")
443 def testText(self):
444 self.go("cheetah fill --flat --oext txt child/a.tmpl")
445 self.checkFill("a.txt")
448 class FlatRecurseCollision(CFBase):
449 expectError = True
451 def testCompile(self):
452 self.goExpectError("cheetah compile -R --flat")
454 def testFill(self):
455 self.goExpectError("cheetah fill -R --flat")
457 def testText(self):
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")
469 def testFill(self):
470 self.go("cheetah fill -R --idir SRC child")
471 self.checkFill("child/a.html")
472 self.checkFill("child/grandkid/a.html")
474 def testText(self):
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")
488 def testFill(self):
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")
493 def testText(self):
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):
500 expectError = True
502 def testCompile(self):
503 self.goExpectError("cheetah compile -R --flat --idir SRC")
505 def testFill(self):
506 self.goExpectError("cheetah fill -R --flat --idir SRC")
508 def testText(self):
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
514 *not* created.
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)
521 def testFill(self):
522 self.go("cheetah fill --nobackup a.tmpl")
523 self.go("cheetah fill --nobackup a.tmpl")
524 self.checkNoBackup("a.html" + BACKUP_SUFFIX)
526 def testText(self):
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 ##################################################
536 ## LIST TESTS ##
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)
545 while 1:
546 lin = f.readline()
547 if not lin:
548 break
549 m = rx.search(lin)
550 if m:
551 print m.group(1)
552 f.close()
554 ##################################################
555 ## MAIN ROUTINE ##
557 class MyOptionParser(OptionParser):
558 """Disable the standard --help and --verbose options since
559 --help is used for another purpose.
561 standard_option_list = []
563 def main():
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()
575 if opts.nodelete:
576 DELETE = False
577 if opts.output:
578 OUTPUT = True
579 if opts.listTests:
580 listTests(opts.listTests)
581 else:
582 # Eliminate script-specific command-line arguments to prevent
583 # errors in unittest.
584 del sys.argv[1:]
585 for opt in ("explain", "help", "verbose", "quiet"):
586 if getattr(opts, opt):
587 sys.argv.append("--" + opt)
588 sys.argv.extend(files)
589 unittest.main()
591 ##################################################
592 ## if run from the command line ##
594 if __name__ == '__main__': main()
596 # vim: sw=4 ts=4 expandtab