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 &lt;prantl@users.sourceforge.net&gt; (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 &lt;prantl@users.sourceforge.net&gt;
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 &lt;prantl@users.sourceforge.net&gt;
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    }