001package org.andromda.maven.plugin.cartridge;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Iterator;
009import java.util.List;
010import junit.framework.Test;
011import junit.framework.TestCase;
012import junit.framework.TestSuite;
013import org.apache.commons.io.FileUtils;
014import org.apache.commons.io.FilenameUtils;
015import org.apache.commons.io.filefilter.TrueFileFilter;
016import org.apache.commons.lang.StringUtils;
017import org.apache.log4j.Logger;
018
019/**
020 * <p>
021 * This is the entry point of the cartridge test suite for AndroMDA. The
022 * test checks for a list of expected files that a file with the same name and
023 * the same package was generated by AndroMDA and that the APIs of the expected
024 * file and the generated file are equal. <code>CartridgeTest</code> acts as
025 * the test director which creates the list of files to be compared. The actual
026 * API comparison is carried out by instances of {@link FileComparator}.
027 * </p>
028 *
029 * @author Ralf Wirdemann
030 * @author Chad Brandon
031 * @author Michail Plushnikov
032 * @author Bob Fields
033 */
034public class CartridgeTest
035    extends TestCase
036{
037    private static final Logger logger = Logger.getLogger(CartridgeTest.class);
038
039    /**
040     * Points to the directory where the expected files are stored which will be
041     * compared to the generated ones.
042     */
043    public static final String EXPECTED_DIRECTORY = "expected.dir";
044
045    /**
046     * Points to the directory where the generated files are located.
047     */
048    public static final String ACTUAL_DIRECTORY = "actual.dir";
049
050    /**
051     * Defines the suffixes of binary files (files that will be not be
052     * compared as strings).
053     */
054    public static final String BINARY_SUFFIXES = "binary.suffixes";
055
056    /**
057     * The shared instance of this class.
058     */
059    private static CartridgeTest instance;
060
061    /**
062     * Retrieves the shared instance of this class.
063     *
064     * @return the shared instance.
065     */
066    public static final CartridgeTest instance()
067    {
068        if (instance == null)
069        {
070            instance = new CartridgeTest();
071        }
072        return instance;
073    }
074
075    private CartridgeTest()
076    {
077        super();
078    }
079
080    /**
081     * Stores a comma separated list of binary suffixes.
082     */
083    private String binarySuffixes = StringUtils.trimToEmpty(System.getProperty(BINARY_SUFFIXES));
084
085    /**
086     * Sets the value of the suffixes that indicate a binary file (binary files
087     * are not compared as text).
088     *
089     * @param binarySuffixes a comma separated list of binary suffixes.
090     */
091    public void setBinarySuffixes(final String binarySuffixes)
092    {
093        this.binarySuffixes = binarySuffixes;
094    }
095
096    /**
097     * Stores the actual directory.
098     */
099    private String actualOutputPath = StringUtils.trimToEmpty(System.getProperty(ACTUAL_DIRECTORY));
100
101    /**
102     * Sets the path to the <em>actual</em> directory (that is the
103     * directory which contains the actual output being tested.
104     *
105     * @param actualOutputPath the path to the actual directory.
106     */
107    public void setActualOutputPath(final String actualOutputPath)
108    {
109        this.actualOutputPath = actualOutputPath;
110    }
111
112    /**
113     * Stores the path to the excepted directory.
114     */
115    private String expectedOutputPath = StringUtils.trimToEmpty(System.getProperty(EXPECTED_DIRECTORY));
116
117    /**
118     * Sets the path to the <em>expected</em> directory.  This is the directory
119     * to which the expected output is extracted.
120     *
121     * @param expectedOutputPath the path to the expected output directory.
122     */
123    public void setExpectedOutputPath(final String expectedOutputPath)
124    {
125        this.expectedOutputPath = expectedOutputPath;
126    }
127
128    /**
129     * @param name
130     */
131    public CartridgeTest(final String name)
132    {
133        super(name);
134    }
135
136    /**
137     * @return suite
138     */
139    public static Test suite()
140    {
141        TestSuite suite = new TestSuite();
142        instance().addTests(suite);
143        return suite;
144    }
145
146    /**
147     * Adds tests which compare all actual generated files against the expected
148     * files.
149     *
150     * @param suite the test suite to which we'll add the tests.
151     */
152    private void addTests(final TestSuite suite)
153    {
154        Collection<File> expectedFiles = Collections.emptyList();
155
156        final File directory = this.getExpectedOutputDirectory();
157        if (null!=directory && directory.exists())
158        {
159            expectedFiles = FileUtils.listFiles(
160                    directory.isDirectory()? directory : directory.getParentFile(),
161                    TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
162        }
163
164        final Iterator<File> iterator = expectedFiles.iterator();
165        logger.info(" --- Expecting " + expectedFiles.size() + " Generated Files --- ");
166        logger.info("ExpectedOutputDirectory --> " + this.getExpectedOutputDirectory());
167        final List<File> missingFiles = new ArrayList<File>();
168        for (int ctr = 1; iterator.hasNext(); ctr++)
169        {
170            final File expectedFile = iterator.next();
171            final File actualFile = getActualFile(expectedFile);
172            if (!actualFile.exists())
173            {
174                missingFiles.add(actualFile);
175            }
176            final boolean binary = isBinary(actualFile);
177            if (logger.isDebugEnabled())
178            {
179                logger.debug(ctr + ") binary = " + binary);
180                logger.debug("expected --> '" + expectedFile + '\'');
181                logger.debug("actual   --> '" + actualFile + '\'');
182            }
183            //Ignore 'generated on ' date comments different between expected/actual
184            // Sometimes it says 'Autogenerated on 04/22/2010 14:49:09-0400 by AndroMDA', sometimes it says 'Generated by ' XX cartridge
185            // Remove the rest of the line from the comparison.
186            // Ignore line ending differences between Windows and Unix
187            List<String> strings = new ArrayList<String>(2);
188            strings.add("enerated by ");
189            strings.add("enerated on ");
190            suite.addTest(new FileComparator(
191                    "testEquals",
192                    expectedFile,
193                    actualFile,
194                    binary,
195                    false,
196                    true,
197                    strings));
198        }
199        if (!missingFiles.isEmpty())
200        {
201            Collections.sort(missingFiles);
202            StringBuilder failureMessage = new StringBuilder("\n--- The following ");
203            failureMessage.append(missingFiles.size());
204            failureMessage.append(" expected files do not exist ----\n");
205            Iterator<File> missingFileIterator = missingFiles.iterator();
206            for (int ctr = 1; missingFileIterator.hasNext(); ctr++)
207            {
208                failureMessage.append(ctr).append(") ");
209                failureMessage.append(missingFileIterator.next());//.append('\n');
210                if (missingFileIterator.hasNext())
211                {
212                    failureMessage.append('\n');
213                }
214            }
215            TestCase.fail(failureMessage.toString());
216        }
217    }
218
219    /**
220     * Constructs the expected file path from the <code>actualFile</code> and
221     * the <code>expectedDir</code> path.
222     *
223     * @param actualFile the actual generated file
224     * @return the new expected file.
225     */
226    private File getActualFile(final File expectedFile)
227    {
228        String actualFile;
229        final File actualOutputDirectory = this.getActualOutputDirectory();
230        final File expectedOutputDirectory = this.getExpectedOutputDirectory();
231        final String path = expectedFile.getPath();
232        if (expectedFile.getPath().startsWith(actualOutputDirectory.getPath()))
233        {
234            actualFile = path.substring(
235                actualOutputDirectory.getPath().length(),
236                    path.length());
237            actualFile = expectedOutputDirectory + expectedFile.toString();
238        }
239        else
240        {
241            actualFile = path.substring(
242                expectedOutputDirectory.getPath().length(),
243                    path.length());
244            actualFile = actualOutputDirectory + actualFile;
245        }
246        return new File(actualFile);
247    }
248
249    /**
250     * The expected output directory.
251     */
252    private File expectedOutputDirectory;
253
254    /**
255     * Retrieves the expected output directory.
256     *
257     * @return the file representing the directory.
258     */
259    private File getExpectedOutputDirectory()
260    {
261       if (this.expectedOutputDirectory == null)
262       {
263           this.expectedOutputDirectory = this.getDirectory(this.expectedOutputPath);
264       }
265       return this.expectedOutputDirectory;
266    }
267
268    /**
269     * Resets the expected output directory.
270     *
271     * @param file the file representing the directory.
272     */
273    protected void setExpectedOutputDirectory(File file)
274    {
275       if (file == null)
276       {
277           this.expectedOutputDirectory = this.getDirectory(this.expectedOutputPath);
278       }
279    }
280
281    /**
282     * The actual output directory.
283     */
284    private File actualOutputDirectory;
285
286    private File getActualOutputDirectory()
287    {
288        if (this.actualOutputDirectory == null)
289        {
290            this.actualOutputDirectory = this.getDirectory(this.actualOutputPath);
291        }
292        return this.actualOutputDirectory;
293    }
294
295    /**
296     * Used when resetting the cartridge test instance, to avoid results overlap
297     * @param file
298     */
299    protected void setActualOutputDirectory(File file)
300    {
301        if (file == null)
302        {
303            this.actualOutputDirectory = this.getDirectory(this.actualOutputPath);
304        }
305    }
306
307    /**
308     * Gets the directory from the system property key.
309     *
310     * @param path the system property key name.
311     * @return the directory as a File instance.
312     */
313    private File getDirectory(final String path)
314    {
315        File directory = new File(path);
316        if (!directory.exists() || !directory.isDirectory())
317        {
318            throw new RuntimeException("directory <" + path + "> doesn't exist");
319        }
320        return directory;
321    }
322
323    /**
324     * Checks whether or not the <code>file</code> is a binary file. Does this
325     * by checking to see if the suffix is found in the list of binary suffixes.
326     *
327     * @param file the file to check
328     * @return true/false
329     */
330    private boolean isBinary(final File file)
331    {
332        String suffix = FilenameUtils.getExtension(file.getName());
333        return this.getBinarySuffixes().contains(suffix);
334    }
335
336    private Collection<String> binarySuffixCollection;
337
338    /**
339     * Gets the binary suffixes for the <code>binary.suffixes</code> system
340     * property. Returns an empty collection if none are found.
341     *
342     * @return the Collection of binary suffixes. (ie. jpg, jar, zip, etc).
343     */
344    private Collection<String> getBinarySuffixes()
345    {
346        if (this.binarySuffixCollection == null)
347        {
348            final String suffixes = this.binarySuffixes != null ? this.binarySuffixes.trim() : "";
349            final String[] suffixArray = suffixes.split("\\s*,\\s*");
350            this.binarySuffixCollection = Arrays.asList(suffixArray);
351        }
352        return this.binarySuffixCollection;
353    }
354
355    /**
356     * Releases any resources held by this cartridge test instance.
357     */
358    public void shutdown()
359    {
360        CartridgeTest.instance = null;
361    }
362}