001 // Copyright 2005-2006 Ferdinand Prantl <prantl@users.sourceforge.net> 002 // Copyright 2001-2004 The Apache Software Foundation 003 // All rights reserved. 004 // 005 // Licensed under the Apache License, Version 2.0 (the "License"); 006 // you may not use this file except in compliance with the License. 007 // You may obtain a copy of the License at 008 // 009 // http://www.apache.org/licenses/LICENSE-2.0 010 // 011 // Unless required by applicable law or agreed to in writing, software 012 // distributed under the License is distributed on an "AS IS" BASIS, 013 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 // See the License for the specific language governing permissions and 015 // limitations under the License. 016 // 017 // See http://ant-eclipse.sourceforge.net for the most recent version 018 // and more information. 019 020 package prantl.ant.eclipse; 021 022 import java.io.File; 023 import java.io.OutputStream; 024 import java.io.PrintStream; 025 026 import org.apache.tools.ant.BuildEvent; 027 import org.apache.tools.ant.BuildException; 028 import org.apache.tools.ant.BuildListener; 029 import org.apache.tools.ant.Project; 030 import org.apache.tools.ant.ProjectHelper; 031 032 import junit.framework.TestCase; 033 034 /** 035 * Simplifies testing of an ant task with a prepared build file automating the task 036 * execution and providing content of the log, standard output and error and an eventual 037 * exception. 038 * 039 * @since Ant-Eclipse 1.0 040 * @author Ferdinand Prantl <prantl@users.sourceforge.net> (based on 041 * org.apache.tools.ant.BuildFileTest from the Ant distribution) 042 */ 043 public abstract class BuildFileTestBase extends TestCase { 044 045 /** The project currently being processed. */ 046 Project project; 047 048 /** The buffer for the captured log messages. */ 049 StringBuffer logBuffer; 050 051 /** The buffer for the captured standard output. */ 052 StringBuffer outBuffer; 053 054 /** The buffer for the captured standard error. */ 055 StringBuffer errBuffer; 056 057 /** The exception eventually thrown during a task execution. */ 058 BuildException buildException; 059 060 /** The maximum log level of the captured traces. */ 061 int logLevel = -1; 062 063 /** The name of the target the captured traces must match. */ 064 String targetName = null; 065 066 /** The name of the task the captured traces must match. */ 067 String taskName = null; 068 069 /** 070 * Passes the construction of a new instance of the test fixture from the descendant 071 * to the ancestor. 072 * 073 * @param name 074 * The name of the test fixture. 075 * @since Ant-Eclipse 1.0 076 * @see TestCase#TestCase(String) 077 */ 078 protected BuildFileTestBase(String name) { 079 super(name); 080 } 081 082 /** 083 * Sets the name of the target, which traces only will be captured. <tt>Null</tt> 084 * turns off the matching, any target will be monitored. 085 * 086 * @param targetName 087 * The name of the target to capture the traces of. 088 * @since Ant-Eclipse 1.0 089 */ 090 protected void setTargetName(String targetName) { 091 this.targetName = targetName; 092 } 093 094 /** 095 * Sets the name of the task, which traces only will be captured. <tt>Null</tt> 096 * turns off the matching, any task will be monitored. 097 * 098 * @param taskName 099 * The name of the target to capture the traces of. 100 * @since Ant-Eclipse 1.0 101 */ 102 protected void setTaskName(String taskName) { 103 this.taskName = taskName; 104 } 105 106 /** 107 * Sets the maximum log level of messages to be captured. <tt>-1</tt> turns off the 108 * matching, any log level will be monitored. 109 * 110 * @param logLevel 111 * Maximum log level of the captured traces. 112 * @since Ant-Eclipse 1.0 113 */ 114 protected void setLogLevel(int logLevel) { 115 this.logLevel = logLevel; 116 } 117 118 /** 119 * Initializes a new project from the specified build file capturing selected traces. 120 * This is usually the first call in a file-oriented test case; most methods of this 121 * class expect this one having been called before. Log capturing can be controlled by 122 * calling setTarget, setTask and setLogLevel. The previous project and captured log 123 * is discarded by calling this method. 124 * 125 * @param fileName 126 * The name of the project file to process. 127 * @throws NullPointerException 128 * If the parameter <tt>fileName</tt> is <tt>null</tt>. 129 * @since Ant-Eclipse 1.0 130 */ 131 protected void parseBuildFile(String fileName) { 132 logBuffer = new StringBuffer(); 133 project = new Project(); 134 project.init(); 135 File buildFile = new File(fileName); 136 project.setUserProperty("ant.file", buildFile.getAbsolutePath()); 137 project.addBuildListener(new LogBuildListener()); 138 ProjectHelper helper = ProjectHelper.getProjectHelper(); 139 project.addReference("ant.projectHelper", helper); 140 helper.parse(project, buildFile); 141 } 142 143 /** 144 * Initializes a new project from the specified build file capturing selected traces 145 * and expecting an exception to be thrown during the parsing. This is usually the 146 * only call in a file-oriented test case; most checking methods of this class expect 147 * this one having been called before. Log capturing can be controlled by calling 148 * setTarget, setTask and setLogLevel. The previous project and captured log is 149 * discarded by calling this method. 150 * 151 * @param fileName 152 * The name of the project file to process. 153 * @param cause 154 * The reason why the exception should have been thrown 155 * @throws NullPointerException 156 * If the parameters <tt>fileName</tt> or <tt>cause</tt> are <tt>null</tt>. 157 * @since Ant-Eclipse 1.0 158 */ 159 protected void parseInvalidBuildFile(String fileName, String cause) { 160 try { 161 parseBuildFile(fileName); 162 fail("BuildException expected because " + cause); 163 } catch (BuildException exception) { 164 buildException = exception; 165 } 166 } 167 168 /** 169 * Executes the specified target expecting a successful run. 170 * 171 * @pre The method parseBuildFile has been called. 172 * @param targetName 173 * The name of the target to execute. 174 * @throws BuildException 175 * If the parameter <tt>targetName</tt> is <tt>null</tt>. 176 * @since Ant-Eclipse 1.0 177 */ 178 protected void executeTarget(String targetName) { 179 PrintStream systemOut = System.out; 180 PrintStream systemErr = System.err; 181 try { 182 logBuffer = new StringBuffer(); 183 outBuffer = new StringBuffer(); 184 systemOut.flush(); 185 System.setOut(new PrintStream(new StringBufferOutputStream(outBuffer))); 186 errBuffer = new StringBuffer(); 187 systemErr.flush(); 188 System.setErr(new PrintStream(new StringBufferOutputStream(errBuffer))); 189 buildException = null; 190 project.executeTarget(targetName); 191 } finally { 192 System.setOut(systemOut); 193 System.setErr(systemErr); 194 } 195 } 196 197 /** 198 * Executes the specified target expecting an exception to be thrown during the run. 199 * 200 * @pre The method parseBuildFile has been called. 201 * @param targetName 202 * The name of the target to execute. 203 * @param cause 204 * The reason why the exception should have been thrown if it had not happened 205 * to be written in a sentence after 'bacause'. 206 * @throws BuildException 207 * If the parameters <tt>targetName</tt> or <tt>cause</tt> are 208 * <tt>null</tt>. 209 * @since Ant-Eclipse 1.0 210 */ 211 protected void executeFailingTarget(String targetName, String cause) { 212 try { 213 executeTarget(targetName); 214 fail("BuildException expected because " + cause); 215 } catch (BuildException exception) { 216 buildException = exception; 217 } 218 } 219 220 /** 221 * Gets the project, which has been most recently initialized or <tt>null</tt> if 222 * there has been none yet. 223 * 224 * @pre The method parseBuildFile has been called. 225 * @return The recently initialized project instance. 226 * @since Ant-Eclipse 1.0 227 */ 228 protected Project getProject() { 229 return project; 230 } 231 232 /** 233 * Gets the most recent log or <tt>null</tt> if there has been none written yet. 234 * 235 * @pre The method parseBuildFile has been called; the methods executeTarget or 236 * executeFailingTarget rewrite the previous content of the buffer. 237 * @return The content of the log. 238 * @since Ant-Eclipse 1.0 239 */ 240 protected String getLog() { 241 return logBuffer.toString(); 242 } 243 244 /** 245 * Gets the most recent standard output or <tt>null</tt> if there has been none 246 * written yet. 247 * 248 * @pre The method parseBuildFile has been called; the methods executeTarget or 249 * executeFailingTarget rewrite the previous content of the buffer. 250 * @return The content of the standard output. 251 * @since Ant-Eclipse 1.0 252 */ 253 protected String getOutput() { 254 return normalizeEolns(outBuffer); 255 } 256 257 /** 258 * Gets the most recent standard error or <tt>null</tt> if there has been none 259 * written yet. 260 * 261 * @pre The method parseBuildFile has been called; the methods executeTarget or 262 * executeFailingTarget rewrite the previous content of the buffer. 263 * @return The content of the standard error. 264 * @since Ant-Eclipse 1.0 265 */ 266 protected String getError() { 267 return normalizeEolns(errBuffer); 268 } 269 270 /** 271 * Gets the most recently thrown exception instance or <tt>null</tt> if there has 272 * been none thrown (the task execution was successful). 273 * 274 * @pre The method parseBuildFile has been called; the methods executeTarget or 275 * executeFailingTarget rewrite the previous content of the buffer. 276 * @return The content of the standard error. 277 * @since Ant-Eclipse 1.0 278 */ 279 protected BuildException getBuildException() { 280 return buildException; 281 } 282 283 /** 284 * Checks that the captured log has the specified content. The comparison is case 285 * sensitive. 286 * 287 * @pre The method parseBuildFile has been called; the methods executeTarget or 288 * executeFailingTarget rewrite the previous content of the buffer. 289 * @param content 290 * The content to be matched. 291 * @since Ant-Eclipse 1.0 292 */ 293 protected void assertLogEquals(String content) { 294 String realLog = getLog(); 295 assertTrue("Log expected to be equal to \"" + content + "\" (actual log \"" 296 + realLog + "\")", realLog.equals(content)); 297 } 298 299 /** 300 * Checks that the captured log has the specified value as a substring. The comparison 301 * is case sensitive. 302 * 303 * @pre The method parseBuildFile has been called; the methods executeTarget or 304 * executeFailingTarget rewrite the previous content of the buffer. 305 * @param part 306 * The part of the content to be searched for. 307 * @since Ant-Eclipse 1.0 308 */ 309 protected void assertLogContains(String part) { 310 String realLog = getLog(); 311 assertTrue("Log expected to contain \"" + part + "\" (actual log \"" + realLog 312 + "\")", realLog.indexOf(part) >= 0); 313 } 314 315 /** 316 * Checks that the captured standard output has the specified content. The comparison 317 * is case sensitive. 318 * 319 * @pre The method parseBuildFile has been called; the methods executeTarget or 320 * executeFailingTarget rewrite the previous content of the buffer. 321 * @param content 322 * The content to be matched. 323 * @since Ant-Eclipse 1.0 324 */ 325 protected void assertOutputEquals(String content) { 326 String realOutput = getOutput(); 327 assertTrue("Output expected to be equal to \"" + content + "\" (actual output \"" 328 + realOutput + "\")", realOutput.equals(content)); 329 } 330 331 /** 332 * Checks that the captured standard output has the specified value as a substring. 333 * The comparison is case sensitive. 334 * 335 * @pre The method parseBuildFile has been called; the methods executeTarget or 336 * executeFailingTarget rewrite the previous content of the buffer. 337 * @param part 338 * The part of the content to be searched for. 339 * @since Ant-Eclipse 1.0 340 */ 341 protected void assertOutputContains(String part) { 342 String realOutput = getOutput(); 343 assertTrue("Log expected to contain \"" + part + "\" (actual log \"" + realOutput 344 + "\")", realOutput.indexOf(part) >= 0); 345 } 346 347 /** 348 * Checks that the captured standard error has the specified content. The comparison 349 * is case sensitive. 350 * 351 * @pre The method parseBuildFile has been called; the methods executeTarget or 352 * executeFailingTarget rewrite the previous content of the buffer. 353 * @param content 354 * The content to be matched. 355 * @since Ant-Eclipse 1.0 356 */ 357 protected void assertErrorEquals(String content) { 358 String realError = getError(); 359 assertTrue("Error expected to be equal to \"" + content + "\" (actual error \"" 360 + realError + "\")", realError.equals(content)); 361 } 362 363 /** 364 * Checks that the captured standard error has the specified value as a substring. The 365 * comparison is case sensitive. 366 * 367 * @pre The method parseBuildFile has been called; the methods executeTarget or 368 * executeFailingTarget rewrite the previous content of the buffer. 369 * @param part 370 * The part of the content to be searched for. 371 * @since Ant-Eclipse 1.0 372 */ 373 protected void assertErrorContains(String part) { 374 String realError = getError(); 375 assertTrue("Error expected to contain \"" + part + "\" (actual error \"" 376 + realError + "\")", realError.indexOf(part) >= 0); 377 } 378 379 /** 380 * Checks that the message of the last thrown exception has the specified value. The 381 * comparison is case sensitive. 382 * 383 * @pre The method parseBuildFile has been called; the methods executeTarget or 384 * executeFailingTarget rewrite the previous content of the buffer. 385 * @param message 386 * The message to be matched. 387 * @since Ant-Eclipse 1.0 388 */ 389 protected void assertBuildExceptionEquals(String message) { 390 String realMessage = getBuildException().getMessage(); 391 assertTrue("BuildException expected with message equal to \"" + message 392 + "\" (actual message \"" + realMessage + "\").", realMessage 393 .equals(message)); 394 } 395 396 /** 397 * Checks that the message of the last thrown exception has the specified value as a 398 * substring. The comparison is case sensitive. 399 * 400 * @pre The method parseBuildFile has been called; the methods executeTarget or 401 * executeFailingTarget rewrite the previous content of the buffer. 402 * @param part 403 * The part of the message to be searched for. 404 * @since Ant-Eclipse 1.0 405 */ 406 protected void assertBuildExceptionContains(String part) { 407 String realMessage = getBuildException().getMessage(); 408 assertTrue("BuildException expected to contain \"" + part 409 + "\" (actual message \"" + realMessage + "\").", realMessage 410 .indexOf(part) >= 0); 411 } 412 413 /** 414 * Checks that the property has the specified value. The comparison is case sensitive. 415 * 416 * @pre The method parseBuildFile has been called; the methods executeTarget or 417 * executeFailingTarget may change the properties. 418 * @param property 419 * The name of the property to check. 420 * @param value 421 * The expected value. 422 * @since Ant-Eclipse 1.0 423 */ 424 protected void assertPropertyEquals(String property, String value) { 425 String result = project.getProperty(property); 426 assertEquals("property " + property, value, result); 427 } 428 429 /** 430 * Checks that the property has the specified value as a substring. The comparison is 431 * case sensitive. 432 * 433 * @pre The method parseBuildFile has been called; the methods executeTarget or 434 * executeFailingTarget may change the properties. 435 * @param property 436 * The name of the property to check. 437 * @param part 438 * The part of the value to be searched for. 439 * @since Ant-Eclipse 1.0 440 */ 441 protected void assertPropertyContains(String property, String part) { 442 String result = project.getProperty(property); 443 assertTrue("The property " + property + " expected to contain \"" + part 444 + "\" (actual value \"" + result + "\").", result.indexOf(part) >= 0); 445 } 446 447 /** 448 * Checks that the property has been set to <tt>true</tt>. 449 * 450 * @pre The method parseBuildFile has been called; the methods executeTarget or 451 * executeFailingTarget may change the properties. 452 * @param property 453 * The name of the property to check. 454 * @since Ant-Eclipse 1.0 455 */ 456 protected void assertPropertyTrue(String property) { 457 assertPropertyEquals(property, "true"); 458 } 459 460 /** 461 * Checks that the property has been set to <tt>false</tt>. 462 * 463 * @pre The method parseBuildFile has been called; the methods executeTarget or 464 * executeFailingTarget may change the properties. 465 * @param property 466 * The name of the property to check. 467 * @since Ant-Eclipse 1.0 468 */ 469 protected void assertPropertyFalse(String property) { 470 assertPropertyEquals(property, "false"); 471 } 472 473 /** 474 * Checks that the property has never been set. 475 * 476 * @pre The method parseBuildFile has been called; the methods executeTarget or 477 * executeFailingTarget may change the properties. 478 * @param property 479 * The name of the property to check. 480 * @since Ant-Eclipse 1.0 481 */ 482 protected void assertPropertyUnset(String property) { 483 assertPropertyEquals(property, null); 484 } 485 486 /** 487 * Returns a new string copy with all possible forms of an end-of-line delimiter made 488 * of carriage-return and/or line-feed (\r, \n, \r\n) replaced by a single line-feed 489 * character (\n). 490 * 491 * @param buffer 492 * The source buffer to normalize eolns in. 493 * @return A new string with the content of the source buffer but with normalized 494 * eolns. 495 * @since Ant-Eclipse 1.0 496 */ 497 protected String normalizeEolns(StringBuffer buffer) { 498 StringBuffer cleanedBuffer = new StringBuffer(); 499 boolean cr = false; 500 for (int i = 0; i < buffer.length(); i++) { 501 char ch = buffer.charAt(i); 502 if (ch == '\r') { 503 cr = true; 504 continue; 505 } else if (ch != '\n') { 506 if (cr) { 507 cleanedBuffer.append('\n'); 508 cr = false; 509 } 510 } else 511 cr = false; 512 513 cleanedBuffer.append(ch); 514 } 515 return cleanedBuffer.toString(); 516 } 517 518 /** 519 * An extension of OutputStream writing every byte as a character in the internal 520 * StringBuffer. 521 * 522 * @since Ant-Eclipse 1.0 523 * @author Ferdinand Prantl <prantl@users.sourceforge.net> 524 */ 525 private static class StringBufferOutputStream extends OutputStream { 526 527 private StringBuffer buffer; 528 529 /** 530 * Creates a new instance of the output stream. Default constructor. 531 * 532 * @param buffer 533 * The output buffer to write into. 534 * @since Ant-Eclipse 1.0 535 */ 536 StringBufferOutputStream(StringBuffer buffer) { 537 this.buffer = buffer; 538 } 539 540 /** 541 * Stores the byte as a character in the internal buffer. 542 * 543 * @see java.io.OutputStream#write(int) 544 * @since Ant-Eclipse 1.0 545 */ 546 public void write(int b) { 547 buffer.append((char) b); 548 } 549 550 } 551 552 /** 553 * An implementation of BuildListener capturing log messages matching the configured 554 * target name, task name and the maximum log level. 555 * 556 * @since Ant-Eclipse 1.0 557 * @author Ferdinand Prantl <prantl@users.sourceforge.net> 558 */ 559 private class LogBuildListener implements BuildListener { 560 561 /** 562 * Creates a new instance of the test listener. Default constructor. 563 * 564 * @since Ant-Eclipse 1.0 565 */ 566 LogBuildListener() { 567 } 568 569 /** 570 * Empty implementation of the according interface method. 571 * 572 * @see BuildListener#buildStarted(BuildEvent) 573 * @since Ant-Eclipse 1.0 574 */ 575 public void buildStarted(BuildEvent event) { 576 } 577 578 /** 579 * Empty implementation of the according interface method. 580 * 581 * @see BuildListener#buildFinished(BuildEvent) 582 * @since Ant-Eclipse 1.0 583 */ 584 public void buildFinished(BuildEvent event) { 585 } 586 587 /** 588 * Empty implementation of the according interface method. 589 * 590 * @see BuildListener#targetStarted(BuildEvent) 591 * @since Ant-Eclipse 1.0 592 */ 593 public void targetStarted(BuildEvent event) { 594 } 595 596 /** 597 * Empty implementation of the according interface method. 598 * 599 * @see BuildListener#targetFinished(BuildEvent) 600 * @since Ant-Eclipse 1.0 601 */ 602 public void targetFinished(BuildEvent event) { 603 } 604 605 /** 606 * Empty implementation of the according interface method. 607 * 608 * @see BuildListener#taskStarted(BuildEvent) 609 * @since Ant-Eclipse 1.0 610 */ 611 public void taskStarted(BuildEvent event) { 612 } 613 614 /** 615 * Empty implementation of the according interface method. 616 * 617 * @see BuildListener#taskFinished(BuildEvent) 618 * @since Ant-Eclipse 1.0 619 */ 620 public void taskFinished(BuildEvent event) { 621 } 622 623 /** 624 * Remembers the logged message if it matches the chosen criteria. 625 * 626 * @param event 627 * The event causing this method to be called. 628 * @see BuildListener#messageLogged(BuildEvent) 629 * @since Ant-Eclipse 1.0 630 */ 631 public void messageLogged(BuildEvent event) { 632 if ((targetName == null || targetName.equals(event.getTarget().getName())) 633 && (taskName == null || taskName 634 .equals(event.getTask().getTaskName())) 635 && (logLevel < 0 || event.getPriority() <= logLevel)) { 636 logBuffer.append('['); 637 logBuffer.append(logLevelPrefixes[event.getPriority()]); 638 logBuffer.append("] ["); 639 logBuffer.append(event.getTarget().getName()); 640 logBuffer.append("] ["); 641 logBuffer.append(event.getTask().getTaskName()); 642 logBuffer.append("] "); 643 logBuffer.append(event.getMessage()); 644 logBuffer.append('\n'); 645 } 646 } 647 648 private final String[] logLevelPrefixes = { "ERROR ", "WARNING", "INFO ", 649 "VERBOSE", "DEBUG " }; 650 651 } 652 653 }