CartridgeTest.java

package org.andromda.maven.plugin.cartridge;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * <p>
 * This is the entry point of the cartridge test suite for AndroMDA. The
 * test checks for a list of expected files that a file with the same name and
 * the same package was generated by AndroMDA and that the APIs of the expected
 * file and the generated file are equal. <code>CartridgeTest</code> acts as
 * the test director which creates the list of files to be compared. The actual
 * API comparison is carried out by instances of {@link FileComparator}.
 * </p>
 *
 * @author Ralf Wirdemann
 * @author Chad Brandon
 * @author Michail Plushnikov
 * @author Bob Fields
 */
public class CartridgeTest
    extends TestCase
{
    private static final Logger logger = Logger.getLogger(CartridgeTest.class);

    /**
     * Points to the directory where the expected files are stored which will be
     * compared to the generated ones.
     */
    public static final String EXPECTED_DIRECTORY = "expected.dir";

    /**
     * Points to the directory where the generated files are located.
     */
    public static final String ACTUAL_DIRECTORY = "actual.dir";

    /**
     * Defines the suffixes of binary files (files that will be not be
     * compared as strings).
     */
    public static final String BINARY_SUFFIXES = "binary.suffixes";

    /**
     * The shared instance of this class.
     */
    private static CartridgeTest instance;

    /**
     * Retrieves the shared instance of this class.
     *
     * @return the shared instance.
     */
    public static final CartridgeTest instance()
    {
        if (instance == null)
        {
            instance = new CartridgeTest();
        }
        return instance;
    }

    private CartridgeTest()
    {
        super();
    }

    /**
     * Stores a comma separated list of binary suffixes.
     */
    private String binarySuffixes = StringUtils.trimToEmpty(System.getProperty(BINARY_SUFFIXES));

    /**
     * Sets the value of the suffixes that indicate a binary file (binary files
     * are not compared as text).
     *
     * @param binarySuffixes a comma separated list of binary suffixes.
     */
    public void setBinarySuffixes(final String binarySuffixes)
    {
        this.binarySuffixes = binarySuffixes;
    }

    /**
     * Stores the actual directory.
     */
    private String actualOutputPath = StringUtils.trimToEmpty(System.getProperty(ACTUAL_DIRECTORY));

    /**
     * Sets the path to the <em>actual</em> directory (that is the
     * directory which contains the actual output being tested.
     *
     * @param actualOutputPath the path to the actual directory.
     */
    public void setActualOutputPath(final String actualOutputPath)
    {
        this.actualOutputPath = actualOutputPath;
    }

    /**
     * Stores the path to the excepted directory.
     */
    private String expectedOutputPath = StringUtils.trimToEmpty(System.getProperty(EXPECTED_DIRECTORY));

    /**
     * Sets the path to the <em>expected</em> directory.  This is the directory
     * to which the expected output is extracted.
     *
     * @param expectedOutputPath the path to the expected output directory.
     */
    public void setExpectedOutputPath(final String expectedOutputPath)
    {
        this.expectedOutputPath = expectedOutputPath;
    }

    /**
     * @param name
     */
    public CartridgeTest(final String name)
    {
        super(name);
    }

    /**
     * @return suite
     */
    public static Test suite()
    {
        TestSuite suite = new TestSuite();
        instance().addTests(suite);
        return suite;
    }

    /**
     * Adds tests which compare all actual generated files against the expected
     * files.
     *
     * @param suite the test suite to which we'll add the tests.
     */
    private void addTests(final TestSuite suite)
    {
        Collection<File> expectedFiles = Collections.emptyList();

        final File directory = this.getExpectedOutputDirectory();
        if (null!=directory && directory.exists())
        {
            expectedFiles = FileUtils.listFiles(
                    directory.isDirectory()? directory : directory.getParentFile(),
                    TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
        }

        final Iterator<File> iterator = expectedFiles.iterator();
        logger.info(" --- Expecting " + expectedFiles.size() + " Generated Files --- ");
        logger.info("ExpectedOutputDirectory --> " + this.getExpectedOutputDirectory());
        final List<File> missingFiles = new ArrayList<File>();
        for (int ctr = 1; iterator.hasNext(); ctr++)
        {
            final File expectedFile = iterator.next();
            final File actualFile = getActualFile(expectedFile);
            if (!actualFile.exists())
            {
                missingFiles.add(actualFile);
            }
            final boolean binary = isBinary(actualFile);
            if (logger.isDebugEnabled())
            {
                logger.debug(ctr + ") binary = " + binary);
                logger.debug("expected --> '" + expectedFile + '\'');
                logger.debug("actual   --> '" + actualFile + '\'');
            }
            //Ignore 'generated on ' date comments different between expected/actual
            // Sometimes it says 'Autogenerated on 04/22/2010 14:49:09-0400 by AndroMDA', sometimes it says 'Generated by ' XX cartridge
            // Remove the rest of the line from the comparison.
            // Ignore line ending differences between Windows and Unix
            List<String> strings = new ArrayList<String>(2);
            strings.add("enerated by ");
            strings.add("enerated on ");
            suite.addTest(new FileComparator(
                    "testEquals",
                    expectedFile,
                    actualFile,
                    binary,
                    false,
                    true,
                    strings));
        }
        if (!missingFiles.isEmpty())
        {
            Collections.sort(missingFiles);
            StringBuilder failureMessage = new StringBuilder("\n--- The following ");
            failureMessage.append(missingFiles.size());
            failureMessage.append(" expected files do not exist ----\n");
            Iterator<File> missingFileIterator = missingFiles.iterator();
            for (int ctr = 1; missingFileIterator.hasNext(); ctr++)
            {
                failureMessage.append(ctr).append(") ");
                failureMessage.append(missingFileIterator.next());//.append('\n');
                if (missingFileIterator.hasNext())
                {
                    failureMessage.append('\n');
                }
            }
            TestCase.fail(failureMessage.toString());
        }
    }

    /**
     * Constructs the expected file path from the <code>actualFile</code> and
     * the <code>expectedDir</code> path.
     *
     * @param actualFile the actual generated file
     * @return the new expected file.
     */
    private File getActualFile(final File expectedFile)
    {
        String actualFile;
        final File actualOutputDirectory = this.getActualOutputDirectory();
        final File expectedOutputDirectory = this.getExpectedOutputDirectory();
        final String path = expectedFile.getPath();
        if (expectedFile.getPath().startsWith(actualOutputDirectory.getPath()))
        {
            actualFile = path.substring(
                actualOutputDirectory.getPath().length(),
                    path.length());
            actualFile = expectedOutputDirectory + expectedFile.toString();
        }
        else
        {
            actualFile = path.substring(
                expectedOutputDirectory.getPath().length(),
                    path.length());
            actualFile = actualOutputDirectory + actualFile;
        }
        return new File(actualFile);
    }

    /**
     * The expected output directory.
     */
    private File expectedOutputDirectory;

    /**
     * Retrieves the expected output directory.
     *
     * @return the file representing the directory.
     */
    private File getExpectedOutputDirectory()
    {
       if (this.expectedOutputDirectory == null)
       {
           this.expectedOutputDirectory = this.getDirectory(this.expectedOutputPath);
       }
       return this.expectedOutputDirectory;
    }

    /**
     * Resets the expected output directory.
     *
     * @param file the file representing the directory.
     */
    protected void setExpectedOutputDirectory(File file)
    {
       if (file == null)
       {
           this.expectedOutputDirectory = this.getDirectory(this.expectedOutputPath);
       }
    }

    /**
     * The actual output directory.
     */
    private File actualOutputDirectory;

    private File getActualOutputDirectory()
    {
        if (this.actualOutputDirectory == null)
        {
            this.actualOutputDirectory = this.getDirectory(this.actualOutputPath);
        }
        return this.actualOutputDirectory;
    }

    /**
     * Used when resetting the cartridge test instance, to avoid results overlap
     * @param file
     */
    protected void setActualOutputDirectory(File file)
    {
        if (file == null)
        {
            this.actualOutputDirectory = this.getDirectory(this.actualOutputPath);
        }
    }

    /**
     * Gets the directory from the system property key.
     *
     * @param path the system property key name.
     * @return the directory as a File instance.
     */
    private File getDirectory(final String path)
    {
        File directory = new File(path);
        if (!directory.exists() || !directory.isDirectory())
        {
            throw new RuntimeException("directory <" + path + "> doesn't exist");
        }
        return directory;
    }

    /**
     * Checks whether or not the <code>file</code> is a binary file. Does this
     * by checking to see if the suffix is found in the list of binary suffixes.
     *
     * @param file the file to check
     * @return true/false
     */
    private boolean isBinary(final File file)
    {
        String suffix = FilenameUtils.getExtension(file.getName());
        return this.getBinarySuffixes().contains(suffix);
    }

    private Collection<String> binarySuffixCollection;

    /**
     * Gets the binary suffixes for the <code>binary.suffixes</code> system
     * property. Returns an empty collection if none are found.
     *
     * @return the Collection of binary suffixes. (ie. jpg, jar, zip, etc).
     */
    private Collection<String> getBinarySuffixes()
    {
        if (this.binarySuffixCollection == null)
        {
            final String suffixes = this.binarySuffixes != null ? this.binarySuffixes.trim() : "";
            final String[] suffixArray = suffixes.split("\\s*,\\s*");
            this.binarySuffixCollection = Arrays.asList(suffixArray);
        }
        return this.binarySuffixCollection;
    }

    /**
     * Releases any resources held by this cartridge test instance.
     */
    public void shutdown()
    {
        CartridgeTest.instance = null;
    }
}