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 }