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