SQLUnit.java :  » Testing » sqlunit » net » sourceforge » sqlunit » Java Open Source

Java Open Source » Testing » sqlunit 
sqlunit » net » sourceforge » sqlunit » SQLUnit.java
/*
 * $Id: SQLUnit.java,v 1.56 2006/04/30 22:25:56 spal Exp $
 * $Source: /cvsroot/sqlunit/sqlunit/src/net/sourceforge/sqlunit/SQLUnit.java,v $
 * SQLUnit - a test harness for unit testing database stored procedures.
 * Copyright (C) 2003  The SQLUnit Team
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package net.sourceforge.sqlunit;

import net.sourceforge.sqlunit.handlers.ConnectionHandler;
import net.sourceforge.sqlunit.utils.DigestUtils;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 * The SQLUnit class is actually a JUnit test case and is the main program
 * which is called by the SQLUnit user. Instead of executing predefined
 * tests in sequence, the SQLUnit test will parse an XML input file containing
 * the test information and execute tests dynamically.
 * @author Sujit Pal (spal@users.sourceforge.net)
 * @version $Revision: 1.56 $
 * @sqlunit.element name="sqlunit"
 *  description="The root tag for an SQLUnit test specification."
 *  syntax="((connection)+, (setup)*, (test | batchtest | diff | echo | func)+, (teardown)*)"
 * @sqlunit.child name="connection"
 *  description="Specifies one or more database Connections that need to be
 *  instantiated for the test to run."
 *  required="Yes, one or more."
 *  ref="connection"
 * @sqlunit.child name="setup"
 *  description="Specifies any setup tasks that need to happen before all
 *  the tests in the suite are run."
 *  required="No"
 *  ref="setup"
 * @sqlunit.child name="test"
 *  description="Specifies a test that needs to run in this suite."
 *  required="Either one or more of test, batchtest, diff, echo or func"
 *  ref="test"
 * @sqlunit.child name="batchtest"
 *  description="Specifies a test that uses the JDBC batching functionality."
 *  required="Either one or more of test, batchtest, diff, echo or func"
 *  ref="batchtest"
 * @sqlunit.child name="diff"
 *  description="Specifies a test that compares the results generated by
 *  two SQL or stored procedure calls"
 *  required="Either one or more of test, batchtest, diff, echo or func"
 *  ref="diff"
 * @sqlunit.child name="echo"
 *  description="Echoes a string after substitution to the log."
 *  required="Either one or more of test, batchtest, diff, echo or func"
 *  ref="echo"
 * @sqlunit.child name="func"
 *  description="Populates a single value returned from a stored procedure
 *  or SQL call into the SymbolTable, identified by function name"
 *  required="Either one or more of test, batchtest, diff, echo or func"
 *  ref="func"
 * @sqlunit.child name="teardown"
 *  description="Specifies any tasks that need to happen after all the tests
 *  in the suite are run"
 *  required="No"
 *  ref="teardown"
 */
public class SQLUnit extends TestCase {

    private static final Logger LOG = Logger.getLogger(SQLUnit.class);

    // passed in from the sqlunit task via the System environment
    private boolean debug = false;
    private boolean haltOnFailure = false;
    private String testFile = null;
    private String errorMessage = null;
    private IReporter reporter = null;

    /**
     * Instantiates a new instance of SQLUnit.
     * @param name the name of the TestCase object.
     */
    public SQLUnit(final String name) {
        super(name);
    }

    /**
     * Returns the TestSuite object to run.
     * @return a Test object.
     */
    public static Test suite() {
        return new TestSuite(SQLUnit.class);
    }

    /**
     * Sets the haltOnFailure attribute from the SQLUnit task.
     * @param haltOnFailure controls the behavior of the test. If true,
     * the test will stop processing the first time a test fails. If
     * false, the test will continue on till all files declared in
     * the sqlunit task is consumed.
     */
    public final void setHaltOnFailure(final boolean haltOnFailure) {
        this.haltOnFailure = haltOnFailure;
    }

    /**
     * Sets the debug attribute from the SQLUnit task.
     * @param debug prints trace information using log4j if set to true.
     */
    public final void setDebug(final boolean debug) {
        Properties log4jProps = new Properties();
        boolean isUsingDefaults = false;
        try {
            ResourceBundle bundle = ResourceBundle.getBundle("log4j");
            for (Enumeration e = bundle.getKeys(); e.hasMoreElements();) {
                String key = (String) e.nextElement();
                String value = bundle.getString(key);
                log4jProps.setProperty(key, value);
            }
        } catch (MissingResourceException e) {
            // resource not found, use defaults
            isUsingDefaults = true;
            log4jProps.setProperty("log4j.rootLogger", "DEBUG, sqlunit");
            log4jProps.setProperty("log4j.appender.sqlunit", 
                "org.apache.log4j.ConsoleAppender");
            log4jProps.setProperty("log4j.appender.sqlunit.layout",
                "org.apache.log4j.PatternLayout");
            log4jProps.setProperty(
                "log4j.appender.sqlunit.layout.ConversionPattern",
                "%5p [%t] (%F:%L) - %m%n");
        }
        PropertyConfigurator.configure(log4jProps);
        Logger logger = Logger.getRootLogger();
        logger.setLevel(debug ? Level.DEBUG : Level.INFO);
        this.debug = debug;
        if (isUsingDefaults) {
            logger.warn("log4j.properties not found (using defaults)");
        }
    }

    /**
     * Sets the IReporter object from the SQLUnit task.
     * @param reporter the IReporter object to log messages to.
     */
    public final void setReporter(final IReporter reporter) {
        this.reporter = reporter;
        SymbolTable.setObject(SymbolTable.REPORTER_KEY, reporter);
    }

    /**
     * Sets the testFile attribute from the SQLUnit task.
     * @param testFile the test file to use for the test.
     */
    public final void setTestFile(final String testFile) {
        this.testFile = testFile;
    }

    /**
     * Allows a client other than the sqlunit build task to set the
     * database connection to use for the test. Useful in case the
     * same file needs to be used to test multiple database instances.
     * @param conn the database connection to use.
     */
    public final void setConnection(final Connection conn) {
        ConnectionRegistry.setConnection(conn);
    }
    
    /**
     * Sets any symbols that were set for SQLUnit within the Ant build.xml
     * file, if SQLUnit is invoked using the SQLUnit Ant task.
     * @param projectProps a Hashtable of Ant properties set in the calling
     * build.xml file.
     */
    public final void setAntSymbols(Hashtable projectProps) {
        for (Enumeration e = projectProps.keys(); e.hasMoreElements();) {
            String key = (String) e.nextElement();
            String value = (String) projectProps.get(key);
            SymbolTable.setValue("${ant." + key + "}", value);
        }
    }

    /**
     * Called by Test.run() or Test.runBare() from the SQLUnit task. Runs
     * the SQLUnit test and collects the results in SQLUnitResult.
     * @exception SQLUnitException if one is thrown by the test.
     */
    public final void runTest() throws SQLUnitException {
        try {
            testWrapper();
        } catch (SQLUnitException e) {
            // SQLUnitException stack trace available only in DEBUG mode
            if (debug) { LOG.fatal(e.getMessage(), e); }
            throw e;
        } catch (Exception e) {
            // unhandled exceptions dump full stack trace in either mode
            LOG.fatal(e.getMessage(), e); 
            throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
                new String[] {"Runtime", e.getClass().getName(), 
                e.getMessage()}, e);
        }
    }

    /**
     * The main testing class which dynamically processes the input XML
     * file. Can throw an exception. Exceptions of class SQLUnitException
     * are treated as application error messages and appear on the STDERR
     * instead of being thrown up in the JVM's stack trace.
     * @exception SQLUnitException if one is thrown by underlying methods.
     */
    public final void testWrapper() throws SQLUnitException {
        LOG.debug(">> testWrapper()");
        SQLUnitResult result = processDoc();
        if (!result.wasSuccessful()) {
            throw new SQLUnitException(IErrorCodes.TEST_FAILURE_EXCEPTION,
                new String[] {result.toString()}, 
                result.getLastException());
        }
    }

    /**
     * Parses the input XML file and processes the directives in sequence
     * and runs the tests requested there. Progress messages appear on the
     * STDERR.
     * @return a SQLUnitResult object.
     * @exception SQLUnitException if there was a problem with the processing.
     */
    private SQLUnitResult processDoc() throws SQLUnitException {
        LOG.debug(">> processDoc()");
        getSystemProperties();
        SQLUnitResult result = new SQLUnitResult();
        TypeMapper mapper = TypeMapper.getTypeMapper();
        result.setTestFile(testFile);
        SAXBuilder builder = new SAXBuilder(true);
        builder.setEntityResolver(new SQLUnitEntityResolver());
        try {
            reporter.newTestFile(basename(testFile), testFile);
            Document doc = builder.build(new FileInputStream(testFile));
            testFile = null;
            Element elRoot = doc.getRootElement();
            // get the connection info (multiple connections allowed)
            if (ConnectionRegistry.getConnection(null) == null) {
                List connectionList = elRoot.getChildren("connection");
                for (int i = 0; i < connectionList.size(); i++) {
                    Element elConnection = (Element) connectionList.get(i);
                    String connectionId = elConnection.getAttributeValue(
                        "connection-id");
                    reporter.settingUpConnection(connectionId);
                    ConnectionHandler connectionHandler = (ConnectionHandler)
                        HandlerFactory.getInstance(elConnection.getName());
                    connectionHandler.process(elConnection);
                    Map config = 
                        connectionHandler.getLastConnectionProperties();
                    config.put("haltOnFailure", haltOnFailure 
                        ? "true" : "false");
                    config.put("debug", debug ? "true" : "false");
                    reporter.setConfig(config);
                }
            }
            // setup the test
            reporter.setUp();
            Element elSetup = elRoot.getChild("setup");
            if (elSetup != null) {
                IHandler setupHandler = 
                    HandlerFactory.getInstance(elSetup.getName());
                setupHandler.process(elSetup);
            }
            // run all the tests, batchtests and diffs in sequence
            List swappableTags = HandlerFactory.getSwappableTags("sqlunit");
            List elTaskList = elRoot.getChildren();
            int testIndex = 0;
            for (int i = 0; i < elTaskList.size(); i++) {
                // if this is not a valid tag for sqlunit, skip it
                Element elTest = (Element) elTaskList.get(i);
                if (!swappableTags.contains(elTest.getName())) {
                    continue;
                }
                testIndex++;
                // if we are told to skip, we should and print a message
                if (shouldSkip(elTest)) {
                    String reason = SymbolTable.getValue(SymbolTable.SKIP_REASON);
                    reporter.skippedTest(elTest.getName(),
                        testIndex, elTest.getAttributeValue("name")
                        + (reason == null ? "" : " (" + reason + ")"));
                    continue;
                }
                if (errorMessage == null || !haltOnFailure) {
                    // accumulate into SQLUnitResult
                    result.addRunCount();
                    // start the test
                    IHandler testHandler = HandlerFactory.getInstance(elTest.getName());
                    Boolean succ = Boolean.FALSE;
                    // print the test report header
                    reporter.runningTest(elTest.getName(), testIndex,
                            elTest.getAttributeValue("name"));
                    try {
                        Object retObj = testHandler.process(elTest);
                        if (retObj instanceof Boolean) {
                            succ = (Boolean) retObj;
                        }
                    } catch (SQLUnitException e) {
                        if (!succ.booleanValue()) {
                            result.addFailureCount();
                        } else {
                            result.addErrorCount();
                        }
                        errorMessage = e.getMessage();
                        result.setLastException(e);
                    }
                    long elapsedTime = 0L;
                    try {
                        elapsedTime = Long.parseLong(SymbolTable.getValue(
                            SymbolTable.TEST_ELAPSED_TIME));
                    } catch (NumberFormatException e) {
                        // :IGNORE: reset to default
                    }
                    reporter.finishedTest(elapsedTime, errorMessage == null);
                    if (errorMessage != null) {
                        reporter.addFailure(result.getLastException(), false);
                    }
                    // clean up temp files, if any
                    Map tempFileMap = DigestUtils.getTempFileMappings();
                    if (tempFileMap.keySet().size() > 0) {
                        Iterator titer = tempFileMap.keySet().iterator();
                        while (titer.hasNext()) {
                            String resultKey = (String) titer.next();
                            String filename = 
                                (String) tempFileMap.get(resultKey);
                            if (filename != null) {
                                if (succ.booleanValue()) {
                                    //test succeeded, delete
                                    File f = new File(filename);
                                    f.delete();
                                } else {
                                    // test failed, log
                                    reporter.tempFile(
                                        testIndex, resultKey, filename);
                                }
                            }
                        }
                    }
                } else {
                    reporter.skippedTest(elTest.getName(), testIndex, 
                        elTest.getAttributeValue("name"));
                }
                errorMessage = null;
            }
            // teardown the test
            reporter.tearDown();
            Element elTeardown = elRoot.getChild("teardown");
            if (elTeardown != null) {
                IHandler teardownHandler = 
                    HandlerFactory.getInstance(elTeardown.getName());
                teardownHandler.process(elTeardown);
            }
            // remove references to user variables in this test
            SymbolTable.removeUserVariables();
            return result;
        } catch (IOException e) {
            reporter.addFailure(e, true);
            errorMessage = e.getMessage();
            throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
                new String[] {"I/O", e.getClass().getName(), 
                e.getMessage()}, e);
        } catch (JDOMException e) {
            reporter.addFailure(e, true);
            errorMessage = e.getMessage();
            throw new SQLUnitException(IErrorCodes.GENERIC_ERROR,
                new String[] {"XML", e.getClass().getName(),
                e.getMessage()}, e);
        } catch (SQLUnitException e) {
            reporter.addFailure(e, true);
            errorMessage = e.getMessage();
            throw e;
        } catch (Exception e) {
            reporter.addFailure(e, true);
            errorMessage = e.getMessage();
            throw new SQLUnitException(IErrorCodes.GENERIC_ERROR, 
                new String[] {"System", e.getClass().getName(),
                e.getMessage()}, e);
        } finally {
            ConnectionRegistry.releaseConnections();
            reporter.testFileComplete(
                (result.errorCount() + result.failureCount()) > 0
                ? false : true);
        }
    }

    private String basename(String testFile) {
        int fs = testFile.lastIndexOf(System.getProperty("file.separator"));
        int dot = testFile.lastIndexOf(".");
        if (dot == -1) dot = testFile.length();
        return testFile.substring(fs + 1, dot);
    }

    /**
     * Convenience method to check the element for the presence of skip and
     * classifiers tags and to check that the test can be run. The method
     * returns true if the test should be skipped, else it returns false.
     * @param elTest the JDOM Element representing the test entity.
     * @return true or false.
     * @exception Exception if one was thrown.
     */
    private boolean shouldSkip(Element elTest) throws Exception {
        Element elSkip = elTest.getChild("skip");
        // if a skip element exists and is true, then it trumps any
        // classifiers
        boolean isSkippable = false;
        if (elSkip != null) {
            IHandler skipHandler = HandlerFactory.getInstance(elSkip.getName());
            isSkippable =
                ((Boolean) skipHandler.process(elSkip)).booleanValue();
            if (isSkippable) {
                return true;
            }
        }
        // if skip is false or non-existent (false), then check classifiers
        Element elClassifiers = elTest.getChild("classifiers");
        boolean isMatched = false;
        if (elClassifiers != null) {
            IHandler classifiersHandler =
                HandlerFactory.getInstance(elClassifiers.getName());
            isMatched = ((Boolean) classifiersHandler.process(elClassifiers)).
                booleanValue();
            if (!isMatched) {
                return true;
            }
        }
        return false;
    }

    /**
     * Pulls the information out of the sqlunit task tag attributes into local
     * variables.
     * @exception SQLUnitException if an error occured.
     */
    private void getSystemProperties() throws SQLUnitException {
        if (testFile == null) {
            throw new SQLUnitException(IErrorCodes.NO_TESTFILE,
                new String[] {IErrorCodes.USAGE});
        }
        File file = new File(testFile);
        if (!file.exists()) {
            throw new SQLUnitException(IErrorCodes.TESTFILE_NOT_FOUND,
                new String[] {IErrorCodes.USAGE});
        }
        // if debug is on, set haltOnFailure to true
        haltOnFailure = (debug ? true : haltOnFailure);
        return;
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.