1   package org.apache.velocity.test;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.StringWriter;
25  
26  import junit.framework.TestCase;
27  
28  import org.apache.oro.text.perl.Perl5Util;
29  import org.apache.velocity.VelocityContext;
30  import org.apache.velocity.app.Velocity;
31  import org.apache.velocity.app.VelocityEngine;
32  import org.apache.velocity.runtime.RuntimeConstants;
33  import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
34  import org.apache.velocity.runtime.resource.util.StringResourceRepository;
35  import org.apache.velocity.test.misc.TestLogChute;
36  import org.apache.velocity.util.StringUtils;
37  
38  /**
39   * Base test case that provides utility methods for
40   * the rest of the tests.
41   *
42   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
43   * @author Nathan Bubna
44   * @version $Id: BaseTestCase.java 898032 2010-01-11 19:51:03Z nbubna $
45   */
46  public abstract class BaseTestCase extends TestCase implements TemplateTestBase
47  {
48      protected VelocityEngine engine;
49      protected VelocityContext context;
50      protected boolean DEBUG = false;
51      protected TestLogChute log;
52      protected String stringRepoName = "string.repo";
53  
54      public BaseTestCase(String name)
55      {
56          super(name);
57  
58          // if we're just running one case, then have DEBUG
59          // automatically set to true
60          String testcase = System.getProperty("testcase");
61          if (testcase != null)
62          {
63              DEBUG = testcase.equals(getClass().getName());
64          }
65      }
66  
67      protected void setUp() throws Exception
68      {
69          engine = new VelocityEngine();
70  
71          //by default, make the engine's log output go to the test-report
72          log = new TestLogChute(false, false);
73          log.setEnabledLevel(TestLogChute.INFO_ID);
74          log.setSystemErrLevel(TestLogChute.WARN_ID);
75          engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, log);
76  
77          // use string resource loader by default, instead of file
78          engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,string");
79          engine.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
80          engine.addProperty("string.resource.loader.repository.name", stringRepoName);
81          engine.addProperty("string.resource.loader.repository.static", "false");
82  
83          setUpEngine(engine);
84  
85          context = new VelocityContext();
86          setUpContext(context);
87      }
88  
89      protected void setUpEngine(VelocityEngine engine)
90      {
91          // extension hook
92      }
93  
94      protected void setUpContext(VelocityContext context)
95      {
96          // extension hook
97      }
98  
99      protected StringResourceRepository getStringRepository()
100     {
101         StringResourceRepository repo =
102             (StringResourceRepository)engine.getApplicationAttribute(stringRepoName);
103         if (repo == null)
104         {
105             engine.init();
106             repo =
107                 (StringResourceRepository)engine.getApplicationAttribute(stringRepoName);
108         }
109         return repo;
110     }
111 
112     protected void addTemplate(String name, String template)
113     {
114         info("Template '"+name+"':  "+template);
115         getStringRepository().putStringResource(name, template);
116     }
117 
118     protected void removeTemplate(String name)
119     {
120         info("Removed: '"+name+"'");
121         getStringRepository().removeStringResource(name);
122     }
123 
124     public void tearDown()
125     {
126         engine = null;
127         context = null;
128     }
129 
130     protected void info(String msg)
131     {
132         if (DEBUG)
133         {
134             if (engine == null)
135             {
136                 Velocity.getLog().info(msg);
137             }
138             else
139             {
140                 engine.getLog().info(msg);
141             }
142         }
143     }
144 
145     protected void info(String msg, Throwable t)
146     {
147         if (DEBUG)
148         {
149             if (engine == null)
150             {
151                 Velocity.getLog().info(msg);
152             }
153             else
154             {
155                 engine.getLog().info(msg, t);
156             }
157         }
158     }
159 
160     public void testBase()
161     {
162         if (DEBUG && engine != null)
163         {
164             assertSchmoo("");
165             assertSchmoo("abc\n123");
166         }
167     }
168 
169     /**
170      * Compare an expected string with the given loaded template
171      */
172     protected void assertTmplEquals(String expected, String template)
173     {        
174         info("Expected:  " + expected + " from '" + template + "'");
175 
176         StringWriter writer = new StringWriter();
177         try
178         {          
179             engine.mergeTemplate(template, "utf-8", context, writer);
180         }
181         catch (RuntimeException re)
182         {
183             info("RuntimeException!", re);
184             throw re;
185         }
186         catch (Exception e)
187         {
188             info("Exception!", e);
189             throw new RuntimeException(e);
190         }        
191 
192         info("Result:  " + writer.toString());
193         assertEquals(expected, writer.toString());  
194     }
195     
196     /**
197      * Ensure that a context value is as expected.
198      */
199     protected void assertContextValue(String key, Object expected)
200     {
201         info("Expected value of '"+key+"': "+expected);
202         Object value = context.get(key);
203         info("Result: "+value);
204         assertEquals(expected, value);
205     }
206 
207     /**
208      * Ensure that a template renders as expected.
209      */
210     protected void assertEvalEquals(String expected, String template)
211     {
212         info("Expectation: "+expected);
213         assertEquals(expected, evaluate(template));
214     }
215 
216     /**
217      * Ensure that the given string renders as itself when evaluated.
218      */
219     protected void assertSchmoo(String templateIsExpected)
220     {
221         assertEvalEquals(templateIsExpected, templateIsExpected);
222     }
223 
224     /**
225      * Ensure that an exception occurs when the string is evaluated.
226      */
227     protected Exception assertEvalException(String evil)
228     {
229         return assertEvalException(evil, null);
230     }
231 
232     /**
233      * Ensure that a specified type of exception occurs when evaluating the string.
234      */
235     protected Exception assertEvalException(String evil, Class exceptionType)
236     {
237         try
238         {
239             if (!DEBUG)
240             {
241                 log.off();
242             }
243             if (exceptionType != null)
244             {
245                 info("Expectation: "+exceptionType.getName());
246             }
247             evaluate(evil);
248             fail("Template '"+evil+"' should have thrown an exception.");
249         }
250         catch (Exception e)
251         {
252             if (exceptionType != null && !exceptionType.isAssignableFrom(e.getClass()))
253             {
254                 fail("Was expecting template '"+evil+"' to throw "+exceptionType+" not "+e);
255             }
256             return e;
257         }
258         finally
259         {
260             if (!DEBUG)
261             {
262                 log.on();
263             }
264         }
265         return null;
266     }
267 
268     /**
269      * Ensure that the error message of the expected exception has the proper location info.
270      */
271     protected Exception assertEvalExceptionAt(String evil, String template,
272                                               int line, int col)
273     {
274         String loc = template+"[line "+line+", column "+col+"]";
275         info("Expectation: Exception at "+loc);
276         Exception e = assertEvalException(evil);
277 
278         info("Result: "+e.getClass().getName()+" - "+e.getMessage());
279         if (e.getMessage().indexOf(loc) < 1)
280         {
281             fail("Was expecting exception at "+loc+" instead of "+e.getMessage());
282         }
283         return e;
284     }
285 
286     /**
287      * Only ensure that the error message of the expected exception
288      * has the proper line and column info.
289      */
290     protected Exception assertEvalExceptionAt(String evil, int line, int col)
291     {
292          return assertEvalExceptionAt(evil, "", line, col);
293     }
294 
295     /**
296      * Evaluate the specified String as a template and return the result as a String.
297      */
298     protected String evaluate(String template)
299     {
300         StringWriter writer = new StringWriter();
301         try
302         {
303             info("Template: "+template);
304 
305             // use template as its own name, since our templates are short
306             // unless it's not that short, then shorten it...
307             String name = (template.length() <= 15) ? template : template.substring(0,15);
308             engine.evaluate(context, writer, name, template);
309 
310             String result = writer.toString();
311             info("Result: "+result);
312             return result;
313         }
314         catch (RuntimeException re)
315         {
316             info("RuntimeException!", re);
317             throw re;
318         }
319         catch (Exception e)
320         {
321             info("Exception!", e);
322             throw new RuntimeException(e);
323         }
324     }
325 
326     /**
327      * Concatenates the file name parts together appropriately.
328      *
329      * @return The full path to the file.
330      */
331     protected String getFileName(final String dir, final String base, final String ext)
332     {
333         return getFileName(dir, base, ext, false);
334     }
335 
336     protected String getFileName(final String dir, final String base, final String ext, final boolean mustExist)
337     {
338         StringBuffer buf = new StringBuffer();
339         try
340         {
341             File baseFile = new File(base);
342             if (dir != null)
343             {
344                 if (!baseFile.isAbsolute())
345                 {
346                     baseFile = new File(dir, base);
347                 }
348 
349                 buf.append(baseFile.getCanonicalPath());
350             }
351             else
352             {
353                 buf.append(baseFile.getPath());
354             }
355 
356             if (org.apache.commons.lang.StringUtils.isNotEmpty(ext))
357             {
358                 buf.append('.').append(ext);
359             }
360 
361             if (mustExist)
362             {
363                 File testFile = new File(buf.toString());
364 
365                 if (!testFile.exists())
366                 {
367                     String msg = "getFileName() result " + testFile.getPath() + " does not exist!";
368                     info(msg);
369                     fail(msg);
370                 }
371 
372                 if (!testFile.isFile())
373                 {
374                     String msg = "getFileName() result " + testFile.getPath() + " is not a file!";
375                     info(msg);
376                     fail(msg);
377                 }
378             }
379         }
380         catch (IOException e)
381         {
382             fail("IO Exception while running getFileName(" + dir + ", " + base + ", "+ ext + ", " + mustExist + "): " + e.getMessage());
383         }
384 
385         return buf.toString();
386     }
387 
388     /**
389      * Assures that the results directory exists.  If the results directory
390      * cannot be created, fails the test.
391      */
392     protected void assureResultsDirectoryExists(String resultsDirectory)
393     {
394         File dir = new File(resultsDirectory);
395         if (!dir.exists())
396         {
397             info("Template results directory ("+resultsDirectory+") does not exist");
398             if (dir.mkdirs())
399             {
400                 info("Created template results directory");
401                 if (DEBUG)
402                 {
403                     info("Created template results directory: "+resultsDirectory);
404                 }
405             }
406             else
407             {
408                 String errMsg = "Unable to create '"+resultsDirectory+"'";
409                 info(errMsg);
410                 fail(errMsg);
411             }
412         }
413     }
414 
415 
416     //TODO: drop this for JDK regex once we move to JDK 1.5
417     private static Perl5Util perl = new Perl5Util();
418     /**
419      * Normalizes lines to account for platform differences.  Macs use
420      * a single \r, DOS derived operating systems use \r\n, and Unix
421      * uses \n.  Replace each with a single \n.
422      *
423      * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a>
424      * @return source with all line terminations changed to Unix style
425      */
426     protected String normalizeNewlines (String source)
427     {
428         return perl.substitute("s/\r[\r]?[\n]/\n/g", source);
429     }
430 
431     /**
432      * Returns whether the processed template matches the
433      * content of the provided comparison file.
434      *
435      * @return Whether the output matches the contents
436      *         of the comparison file.
437      *
438      * @exception Exception Test failure condition.
439      */
440     protected boolean isMatch (String resultsDir,
441                                String compareDir,
442                                String baseFileName,
443                                String resultExt,
444                                String compareExt) throws Exception
445     {
446         String result = getFileContents(resultsDir, baseFileName, resultExt);
447         return isMatch(result,compareDir,baseFileName,compareExt);
448     }
449 
450 
451     protected String getFileContents(String dir, String baseFileName, String ext)
452     {
453         String fileName = getFileName(dir, baseFileName, ext, true);
454         return StringUtils.fileContentsToString(fileName);
455     }
456 
457     /**
458      * Returns whether the processed template matches the
459      * content of the provided comparison file.
460      *
461      * @return Whether the output matches the contents
462      *         of the comparison file.
463      *
464      * @exception Exception Test failure condition.
465      */
466     protected boolean isMatch (String result,
467                                String compareDir,
468                                String baseFileName,
469                                String compareExt) throws Exception
470     {
471         String compare = getFileContents(compareDir, baseFileName, compareExt);
472 
473         // normalize each wrt newline
474         result = normalizeNewlines(result);
475         compare = normalizeNewlines(compare);
476         if (DEBUG)
477         {
478             info("Expection: "+compare);
479             info("Result: "+result);
480         }
481         return result.equals(compare);
482     }
483 
484     /**
485      * Turns a base file name into a test case name.
486      *
487      * @param s The base file name.
488      * @return  The test case name.
489      */
490     protected static final String getTestCaseName(String s)
491     {
492         StringBuffer name = new StringBuffer();
493         name.append(Character.toTitleCase(s.charAt(0)));
494         name.append(s.substring(1, s.length()).toLowerCase());
495         return name.toString();
496     }
497 }