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.ByteArrayInputStream;
023    import java.io.ByteArrayOutputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    import java.io.OutputStream;
028    import java.io.PrintStream;
029    import java.util.Arrays;
030    import java.util.HashSet;
031    import java.util.Hashtable;
032    import java.util.StringTokenizer;
033    
034    import junit.framework.TestCase;
035    
036    import org.apache.tools.ant.BuildEvent;
037    import org.apache.tools.ant.BuildListener;
038    import org.apache.tools.ant.Project;
039    
040    /**
041     * Test fixture with unit test cases for the class <tt>EclipseTask</tt>.
042     * 
043     * @see EclipseTask
044     * @since Ant-Eclipse 1.0
045     * @author Ferdinand Prantl &lt;prantl@users.sourceforge.net&gt;
046     */
047    public class EclipseTaskTest extends TestCase {
048    
049        /**
050         * Creates a new instance of the test fixture. Default constructor.
051         * 
052         * @since Ant-Eclipse 1.0
053         */
054        public EclipseTaskTest() {
055        }
056    
057        /**
058         * Helper class constructing the task object for the class <tt>EclipseTask</tt> with
059         * the minimum environment simulating an ant-build file with a single target with a
060         * single task.
061         * 
062         * @see EclipseTask
063         * @since Ant-Eclipse 1.0
064         * @author Ferdinand Prantl &lt;prantl@users.sourceforge.net&gt;
065         */
066        static final class EclipseTaskTester extends EclipseTask {
067    
068            /**
069             * Creates a new instance of the task with the minimum ant-like environment.
070             * 
071             * @param object
072             *        An object that the output is delegated into.
073             * @throws NullPointerException
074             *         If the parameter <tt>object</tt> is null.
075             * @since Ant-Eclipse 1.0
076             */
077            EclipseTaskTester(EclipseOutput object) {
078                super(object);
079                setProject(new Project());
080                getProject().init();
081                getProject().setUserProperty("ant.project.name", "eclipse");
082                // setTaskType("eclipse");
083                // setTaskName("eclipse");
084                // setOwningTarget(new Target());
085            }
086    
087        }
088    
089        /**
090         * Testing output class performing output into in-memory streams.
091         * 
092         * @see EclipseOutput
093         * @since Ant-Eclipse 1.0
094         * @author Ferdinand Prantl &lt;prantl@users.sourceforge.net&gt;
095         */
096        static final class MemoryEclipseOutput extends EclipseOutput {
097    
098            private Hashtable settings = new Hashtable();
099    
100            private ByteArrayOutputStream project = null;
101    
102            private ByteArrayOutputStream classPath = null;
103    
104            /**
105             * Creates a new instance of the output object.
106             * 
107             * @param element
108             *        An object containing the configuration.
109             * @see EclipseOutput#EclipseOutput(EclipseElement)
110             * @since Ant-Eclipse 1.0
111             */
112            MemoryEclipseOutput(EclipseElement element) {
113                super(element);
114            }
115    
116            /**
117             * @see EclipseOutput#isPreferencesUpToDate(String)
118             * @since Ant-Eclipse 1.0
119             */
120            boolean isPreferencesUpToDate(String name) {
121                return false;
122            }
123    
124            /**
125             * @see EclipseOutput#isProjectUpToDate()
126             * @since Ant-Eclipse 1.0
127             */
128            boolean isProjectUpToDate() {
129                return false;
130            }
131    
132            /**
133             * @see EclipseOutput#isClassPathUpToDate()
134             * @since Ant-Eclipse 1.0
135             */
136            boolean isClassPathUpToDate() {
137                return false;
138            }
139    
140            /**
141             * @see EclipseOutput#openPreferences(String)
142             * @since Ant-Eclipse 1.0
143             */
144            InputStream openPreferences(String name) {
145                ByteArrayOutputStream preferences = (ByteArrayOutputStream) settings
146                        .get(name);
147                return preferences == null ? null : new ByteArrayInputStream(preferences
148                        .toByteArray());
149            }
150    
151            /**
152             * @see EclipseOutput#openProject()
153             * @since Ant-Eclipse 1.0
154             */
155            InputStream openProject() {
156                return project == null ? null : new ByteArrayInputStream(project
157                        .toByteArray());
158            }
159    
160            /**
161             * @see EclipseOutput#openClassPath()
162             * @since Ant-Eclipse 1.0
163             */
164            InputStream openClassPath() {
165                return classPath == null ? null : new ByteArrayInputStream(classPath
166                        .toByteArray());
167            }
168    
169            /**
170             * @see EclipseOutput#createPreferences(String)
171             * @since Ant-Eclipse 1.0
172             */
173            OutputStream createPreferences(String name) {
174                ByteArrayOutputStream preferences = new ByteArrayOutputStream();
175                settings.put(name, preferences);
176                return preferences;
177            }
178    
179            /**
180             * @see EclipseOutput#createProject()
181             * @since Ant-Eclipse 1.0
182             */
183            OutputStream createProject() {
184                return project = new ByteArrayOutputStream();
185            }
186    
187            /**
188             * @see EclipseOutput#createClassPath()
189             * @since Ant-Eclipse 1.0
190             */
191            OutputStream createClassPath() {
192                return classPath = new ByteArrayOutputStream();
193            }
194    
195        }
196    
197        /**
198         * Testing build listener collecting the log messages up to the specified level.
199         * 
200         * @since Ant-Eclipse 1.0
201         * @author Ferdinand Prantl &lt;prantl@users.sourceforge.net&gt;
202         */
203        static final class MemoryLogListener implements BuildListener {
204    
205            private StringBuffer log = new StringBuffer();
206    
207            private int logLevel;
208    
209            /**
210             * Constructs a test listener which will store the log events below or equal the
211             * given level.
212             * 
213             * @param logLevel
214             *        Maximum logLevel to watch.
215             */
216            public MemoryLogListener(int logLevel) {
217                this.logLevel = logLevel;
218            }
219    
220            /**
221             * @return Collected log messages.
222             */
223            String getLog() {
224                return log.toString();
225            }
226    
227            /**
228             * Fired before any targets are started.
229             * 
230             * @param event
231             *        Context information about the event.
232             */
233            public void buildStarted(BuildEvent event) {
234            }
235    
236            /**
237             * Fired after the last target has finished. This event will still be thrown if an
238             * error occured during the build.
239             * 
240             * @param event
241             *        Context information about the event.
242             */
243            public void buildFinished(BuildEvent event) {
244            }
245    
246            /**
247             * Fired when a target is started.
248             * 
249             * @param event
250             *        Context information about the event.
251             */
252            public void targetStarted(BuildEvent event) {
253            }
254    
255            /**
256             * Fired when a target has finished. This event will still be thrown if an error
257             * occured during the build.
258             * 
259             * @param event
260             *        Context information about the event.
261             */
262            public void targetFinished(BuildEvent event) {
263            }
264    
265            /**
266             * Fired when a task is started.
267             * 
268             * @param event
269             *        Context information about the event.
270             */
271            public void taskStarted(BuildEvent event) {
272            }
273    
274            /**
275             * Fired when a task has finished. This event will still be thrown if an error
276             * occured during the build.
277             * 
278             * @param event
279             *        Context information about the event.
280             */
281            public void taskFinished(BuildEvent event) {
282            }
283    
284            /**
285             * Fired whenever a message is logged.
286             * 
287             * @param event
288             *        Context information about the event.
289             */
290            public void messageLogged(BuildEvent event) {
291                if (event.getPriority() <= logLevel) {
292                    log.append(levels[event.getPriority()]);
293                    log.append(' ');
294                    log.append(event.getMessage());
295                    log.append('\n');
296                }
297            }
298    
299            private static final String[] levels = new String[] { "ERROR  ", "WARNING",
300                    "INFO   ", "VERBOSE", "DEBUG  " };
301    
302        }
303    
304        /**
305         * Tests executing the task with the following configuration:
306         * 
307         * <pre>
308         *   &lt;eclipse /&gt;
309         * </pre>
310         * 
311         * @throws Exception
312         *         If the task execution fails.
313         */
314        public void testExecuteWithEmptyEclipseElement() throws Exception {
315            MemoryEclipseOutput output = new MemoryEclipseOutput(new EclipseElement());
316            EclipseTaskTester task = new EclipseTaskTester(output);
317            MemoryLogListener logListener = new MemoryLogListener(Project.MSG_VERBOSE);
318            task.getProject().addBuildListener(logListener);
319    
320            ByteArrayOutputStream out = new ByteArrayOutputStream();
321            ByteArrayOutputStream err = new ByteArrayOutputStream();
322            PrintStream systemOut = System.out;
323            PrintStream systemErr = System.err;
324            try {
325                systemOut.flush();
326                systemErr.flush();
327                System.setOut(new PrintStream(out));
328                System.setErr(new PrintStream(err));
329                task.execute();
330            } finally {
331                System.setOut(systemOut);
332                System.setErr(systemErr);
333            }
334    
335            assertEquals(0, out.size());
336            assertEquals(0, err.size());
337            assertNull(output.openPreferences(OrgEclipseCoreResourcesPreferencesElement
338                    .getPackageName()));
339            assertNull(output.openPreferences(OrgEclipseCoreRuntimePreferencesElement
340                    .getPackageName()));
341            assertNull(output.openPreferences(OrgEclipseJdtCorePreferencesElement
342                    .getPackageName()));
343            assertNull(output.openPreferences(OrgEclipseJdtUiPreferencesElement
344                    .getPackageName()));
345            assertNull(output.openProject());
346            assertNull(output.openClassPath());
347            assertEquals("WARNING There were no settings found.\n"
348                    + "WARNING There was no description of a project found.\n"
349                    + "WARNING There was no description of a classpath found.\n", logListener
350                    .getLog());
351        }
352    
353        /**
354         * Tests executing the task with the following configuration:
355         * 
356         * <pre>
357         *   &lt;eclipse&gt;
358         *     &lt;settings&gt;
359         *       &lt;resources version=&quot;2&quot; encoding=&quot;UTF-8&quot; /&gt;
360         *     &lt;/settings&gt;
361         *   &lt;/eclipse&gt;
362         * </pre>
363         * 
364         * @throws Exception
365         *         If the task execution fails.
366         */
367        public void testExecuteWithSettingsElementWithVersionAndEncoding() throws Exception {
368            EclipseElement eclipse = new EclipseElement();
369            SettingsElement settings = new SettingsElement();
370            OrgEclipseCoreResourcesPreferencesElement resources = settings.createResources();
371            resources.setVersion("2");
372            resources.setEncoding("UTF-8");
373            eclipse.setSettings(settings);
374    
375            MemoryEclipseOutput output = new MemoryEclipseOutput(eclipse);
376            EclipseTaskTester task = new EclipseTaskTester(output);
377            MemoryLogListener logListener = new MemoryLogListener(Project.MSG_VERBOSE);
378            task.getProject().addBuildListener(logListener);
379            task.execute();
380    
381            String settingsOutput = streamToString(output
382                    .openPreferences(OrgEclipseCoreResourcesPreferencesElement
383                            .getPackageName()));
384            // "#Fri Aug 19 07:58:50 CEST 2005\n"
385            assertTrue(settingsOutput.startsWith("#"));
386            assertTrue(settingsOutput.endsWith("\n"));
387            assertEqualAllLines("encoding/<project>=UTF-8\n"
388                    + "eclipse.preferences.version=2\n", skipLine(settingsOutput));
389            assertNull(output.openPreferences(OrgEclipseCoreRuntimePreferencesElement
390                    .getPackageName()));
391            assertNull(output.openPreferences(OrgEclipseJdtCorePreferencesElement
392                    .getPackageName()));
393            assertNull(output.openPreferences(OrgEclipseJdtUiPreferencesElement
394                    .getPackageName()));
395            assertNull(output.openProject());
396            assertNull(output.openClassPath());
397            assertEquals(
398                    "INFO    Writing the preferences for \"org.eclipse.core.resources\".\n"
399                            + "WARNING There was no description of a project found.\n"
400                            + "WARNING There was no description of a classpath found.\n",
401                    logListener.getLog());
402        }
403    
404        /**
405         * Tests executing the task with the following configuration:
406         * 
407         * <pre>
408         *   &lt;eclipse&gt;
409         *     &lt;project /&gt;
410         *   &lt;/eclipse&gt;
411         * </pre>
412         * 
413         * @throws Exception
414         *         If the task execution fails.
415         */
416        public void testExecuteWithEmptyProjectElement() throws Exception {
417            EclipseElement eclipse = new EclipseElement();
418            eclipse.setProject(new ProjectElement());
419    
420            MemoryEclipseOutput output = new MemoryEclipseOutput(eclipse);
421            EclipseTaskTester task = new EclipseTaskTester(output);
422            MemoryLogListener logListener = new MemoryLogListener(Project.MSG_VERBOSE);
423            task.getProject().addBuildListener(logListener);
424            task.execute();
425    
426            assertNull(output.openPreferences(OrgEclipseCoreResourcesPreferencesElement
427                    .getPackageName()));
428            assertNull(output.openPreferences(OrgEclipseCoreRuntimePreferencesElement
429                    .getPackageName()));
430            assertNull(output.openPreferences(OrgEclipseJdtCorePreferencesElement
431                    .getPackageName()));
432            assertNull(output.openPreferences(OrgEclipseJdtUiPreferencesElement
433                    .getPackageName()));
434            String projectOutput = streamToString(output.openProject());
435            assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
436                    + "<projectDescription>\n" + "  <name>eclipse</name>\n" + "  <comment>\n"
437                    + "  </comment>\n" + "  <projects>\n" + "  </projects>\n"
438                    + "  <buildSpec>\n" + "    <buildCommand>\n"
439                    + "      <name>org.eclipse.jdt.core.javabuilder</name>\n"
440                    + "      <arguments>\n" + "      </arguments>\n"
441                    + "    </buildCommand>\n" + "  </buildSpec>\n" + "  <natures>\n"
442                    + "    <nature>org.eclipse.jdt.core.javanature</nature>\n"
443                    + "  </natures>\n" + "</projectDescription>", projectOutput);
444            assertNull(output.openClassPath());
445            assertEquals("WARNING There were no settings found.\n"
446                    + "INFO    Writing the project definition in the mode \"java\".\n"
447                    + "VERBOSE Project name is \"eclipse\".\n"
448                    + "WARNING There was no description of a classpath found.\n", logListener
449                    .getLog());
450        }
451    
452        /**
453         * Tests executing the task with the following configuration:
454         * 
455         * <pre>
456         *   &lt;eclipse&gt;
457         *     &lt;project /&gt;
458         *   &lt;/eclipse&gt;
459         * </pre>
460         * 
461         * @throws Exception
462         *         If the task execution fails.
463         */
464        public void testExecuteWithProjectElementWithName() throws Exception {
465            EclipseElement eclipse = new EclipseElement();
466            ProjectElement project = new ProjectElement();
467            project.setName("test");
468            eclipse.setProject(project);
469    
470            MemoryEclipseOutput output = new MemoryEclipseOutput(eclipse);
471            EclipseTaskTester task = new EclipseTaskTester(output);
472            MemoryLogListener logListener = new MemoryLogListener(Project.MSG_VERBOSE);
473            task.getProject().addBuildListener(logListener);
474            task.execute();
475    
476            assertNull(output.openPreferences(OrgEclipseCoreResourcesPreferencesElement
477                    .getPackageName()));
478            assertNull(output.openPreferences(OrgEclipseCoreRuntimePreferencesElement
479                    .getPackageName()));
480            assertNull(output.openPreferences(OrgEclipseJdtCorePreferencesElement
481                    .getPackageName()));
482            assertNull(output.openPreferences(OrgEclipseJdtUiPreferencesElement
483                    .getPackageName()));
484            String projectOutput = streamToString(output.openProject());
485            assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
486                    + "<projectDescription>\n" + "  <name>test</name>\n" + "  <comment>\n"
487                    + "  </comment>\n" + "  <projects>\n" + "  </projects>\n"
488                    + "  <buildSpec>\n" + "    <buildCommand>\n"
489                    + "      <name>org.eclipse.jdt.core.javabuilder</name>\n"
490                    + "      <arguments>\n" + "      </arguments>\n"
491                    + "    </buildCommand>\n" + "  </buildSpec>\n" + "  <natures>\n"
492                    + "    <nature>org.eclipse.jdt.core.javanature</nature>\n"
493                    + "  </natures>\n" + "</projectDescription>", projectOutput);
494            assertNull(output.openClassPath());
495            assertEquals("WARNING There were no settings found.\n"
496                    + "INFO    Writing the project definition in the mode \"java\".\n"
497                    + "VERBOSE Project name is \"test\".\n"
498                    + "WARNING There was no description of a classpath found.\n", logListener
499                    .getLog());
500        }
501    
502        private String streamToString(InputStream input) throws IOException {
503            InputStreamReader reader = new InputStreamReader(input, "UTF-8");
504            StringBuffer content = new StringBuffer();
505            for (int ch; (ch = reader.read()) != -1;)
506                content.append((char) ch);
507            return content.toString();
508        }
509    
510        private String skipLine(String input) {
511            int next = input.indexOf('\n');
512            return next >= 0 ? input.substring(next + 1) : "";
513        }
514    
515        private void assertEqualAllLines(String expected, String actual) {
516            HashSet expectedLines = new HashSet(Arrays.asList(split(expected, "\n")));
517            String[] actualLines = split(actual, "\n");
518            for (int i = 0; i != actualLines.length; ++i)
519                if (!expectedLines.remove(actualLines[i]))
520                    fail("The line " + (i + 1) + " (\"" + actualLines[i]
521                            + ") from the actual content \"" + actual
522                            + "\" does not match any in the expected content \"" + expected
523                            + "\".");
524            int size = expectedLines.size();
525            if (size != 0)
526                fail(size + " line(s) ("
527                        + join((String[]) expectedLines.toArray(new String[] {}), "\n")
528                        + ") from the expected content \"" + expected
529                        + "\" were missing in the actual content \"" + actual + "\".");
530        }
531    
532        /**
533         * Splits the input string into an array of strings using the specified delimiter.
534         * 
535         * @param input
536         *        The input string to be splitted.
537         * @param delimiter
538         *        The delimiting token.
539         * @throws NullPointerException
540         *         If some of the input parameters are null.
541         * @return An array with strings as delimited parts of the input.
542         */
543        public static String[] split(String input, String delimiter) {
544            StringTokenizer tokenizer = new StringTokenizer(input, delimiter);
545            String[] result = new String[tokenizer.countTokens()];
546            for (int i = 0; tokenizer.hasMoreTokens(); ++i)
547                result[i] = tokenizer.nextToken();
548            return result;
549        }
550    
551        /**
552         * Splits the input array into a string using the specified delimiter.
553         * 
554         * @param input
555         *        The input array to be joined.
556         * @param delimiter
557         *        The delimiting token.
558         * @throws NullPointerException
559         *         If some of the input parameters are null.
560         * @return An string joined with the specified delimiter from he input array.
561         */
562        public static String join(String[] input, String delimiter) {
563            StringBuffer result = new StringBuffer();
564            for (int i = 0; i != input.length; ++i) {
565                if (i != 0)
566                    result.append(delimiter);
567                result.append(input[i]);
568            }
569            return result.toString();
570        }
571    
572    }