/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 2000, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 *
 * TestImpl.java
 *
 */
package org.apache.qetest;

import java.util.Properties;

/**
 * Minimal class defining a test implementation, using a Reporter.
 * <p>TestImpls generally interact with a Reporter, which reports
 * out in various formats the results from this test.
 * Most test classes should subclass from this test, as it adds
 * structure that helps to define the conceptual logic of running
 * a 'test'.  It also provides useful default implementations.</p>
 * <p>Users wishing a much simpler testing framework can simply
 * implement the minimal methods in the Test interface, and use a
 * Logger to report results instead of a Reporter.</p>
 * @author Shane_Curcuru@lotus.com
 * @version $Id$
 */
public class TestImpl implements Test
{

    /**
     * Name (and description) of the current test.
     * <p>Note that these are merely convenience variables - you do not need
     * to use them.  If you do use them, they should be initialized at
     * construction time.</p>
     */
    protected String testName = null;

    /**
     * Accesor method for the name of this test.  
     *
     * NEEDSDOC ($objectName$) @return
     */
    public String getTestName()
    {
        return testName;
    }

    /** (Name and) description of the current test. */
    protected String testComment = null;

    /**
     * Accesor method for a brief description of this test.  
     *
     * NEEDSDOC ($objectName$) @return
     */
    public String getTestDescription()
    {
        return testComment;
    }

    /**
     * Default constructor - initialize testName, Comment.
     */
    public TestImpl()
    {

        // Only set them if they're not set
        if (testName == null)
            testName = "TestImpl.defaultName";

        if (testComment == null)
            testComment = "TestImpl.defaultComment";
    }

    /** Our Logger, who we tell all our secrets to. */
    protected Logger logger = null;

    /**
     * Accesor methods for our Logger.  
     *
     * NEEDSDOC @param l
     */
    public void setLogger(Logger l)
    {  // no-op: our implementation always uses a Reporter
    }

    /**
     * Accesor methods for our Logger.  
     *
     * NEEDSDOC ($objectName$) @return
     */
    public Logger getLogger()
    {
        return null;
    }

    /** Our Reporter, who we tell all our secrets to. */
    protected Reporter reporter;

    /**
     * Accesor methods for our Reporter.  
     *
     * NEEDSDOC @param r
     */
    public void setReporter(Reporter r)
    {
        if (r != null)
            reporter = r;
    }

    /**
     * Accesor methods for our Reporter.  
     *
     * NEEDSDOC ($objectName$) @return
     */
    public Reporter getReporter()
    {
        return reporter;
    }

    /** Flag to indicate a serious enough error that we should just give up. */
    protected boolean abortTest = false;

    /**
     * Accesor methods for our abort flag.  
     *
     * NEEDSDOC @param a
     */
    public void setAbortTest(boolean a)
    {
        abortTest = a;
    }

    /**
     * Accesor methods for our abort flag.  
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean getAbortTest()
    {
        return (abortTest);
    }

    /**
     * Run this test: main interface to cause the test to run itself.
     * <p>A major goal of the TestImpl class is to separate the act and process
     * of writing a test from it's actual runtime implementation.  Testwriters
     * should not need to know how their test is being executed.</p>
     * <ul>They should simply focus on defining:
     * <li>doTestFileInit: what setup has to be done before running the test</li>
     * <li>testCase1, 2, ... n: individual, independent test cases</li>
     * <li>doTestFileClose: what cleanup has to be done after running the test</li>
     * </ul>
     * <p>This method returns a simple boolean status as a convenience.  In cases
     * where you have a harness that runs a great many tests that normally pass, the
     * harness can simply check this value for each test: if it's true, you could
     * even delete any result logs then, and simply print out a meta-log stating
     * that the test passed.  Note that this does not provide any information about
     * why a test failed (or caused an error, or whatever) - that's what the info in
     * any Reporter's logs are for.</p>
     * <p>If a test is aborted, then any containing harness needs not
     * finish executing the test.  Otherwise, even if part of a test fails,
     * you should let the whole test run through.</p>
     * <p>Harnesses should generally simply call runTest() to ask the
     * test to run itself.  In some cases a Harness might want to control
     * the process more closely, in which case it should call:
     * <code>
     *  test.setReporter(); // optional, depending on the test
     *  test.testFileInit();
     *  test.runTestCases();
     *  test.testFileClose();
     * </code>  instead.
     * @todo return TestResult instead of boolean flag
     * @author Shane_Curcuru@lotus.com
     *
     * NEEDSDOC @param p
     * @return status - true if test ran to completion and <b>all</b>
     * cases passed, false otherwise
     */
    public boolean runTest(Properties p)
    {

        boolean status = testFileInit(p);

        if (getAbortTest())
            return status;

        status &= runTestCases(p);

        if (getAbortTest())
            return status;

        status &= testFileClose(p);

        return status;
    }

    /**
     * Initialize this test - called once before running testcases.
     * Predefined behavior - subclasses should <b>not</b> override this method.
     * <p>This method is basically a composite that masks the most common
     * implementation: creating a reporter or logger first, then initializing
     * any data or product settings the test needs setup first. It does this
     * by separating this method into three methods:
     * <code>
     *   preTestFileInit(); // Create/initialize Reporter
     *   doTestFileInit();  // User-defined: initialize product under test
     *   postTestFileInit() // Report out we've completed initialization
     * </code>
     * </p>
     * @author Shane_Curcuru@lotus.com
     * @see #preTestFileInit(java.util.Properties)
     * @see #doTestFileInit(java.util.Properties)
     * @see #postTestFileInit(java.util.Properties)
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean testFileInit(Properties p)
    {

        // Note: we don't want to use shortcut operators here,
        //       since we want each method to get called
        // Pass the Property block to each method, so that 
        //       subclasses can do initialization whenever 
        //       is best for their design
        return preTestFileInit(p) & doTestFileInit(p) & postTestFileInit(p);
    }

    /**
     * Initialize this test - called once before running testcases.
     * <p>Create and initialize a Reporter here.</p>
     * <p>This implementation simply creates a default Reporter
     * and adds a ConsoleLogger. Most test groups will want to override
     * this method to create custom Reporters or Loggers.</p>
     * @author Shane_Curcuru@lotus.com
     * @see #testFileInit(java.util.Properties)
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean preTestFileInit(Properties p)
    {

        // Pass our properties block directly to the reporter
        //  so it can use the same values in initialization
        setReporter(new Reporter(p));
        reporter.addDefaultLogger();
        reporter.testFileInit(testName, testComment);

        return true;
    }

    /**
     * Initialize this test - called once before running testcases.
     * <p>Subclasses <b>must</b> override this to do whatever specific
     * processing they need to initialize their product under test.</p>
     * <p>If for any reason the test should not continue, it <b>must</b>
     * return false from this method.</p>
     * @author Shane_Curcuru@lotus.com
     * @see #testFileInit(java.util.Properties)
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean doTestFileInit(Properties p)
    {

        // @todo implement in your subclass
        reporter.logTraceMsg(
            "TestImpl.doTestFileInit() default implementation - please override");

        return true;
    }

    /**
     * Initialize this test - called once before running testcases.
     * <p>Simply log out that our initialization has completed,
     * so that structured-style logs will make it clear where startup
     * code ends and testCase code begins.</p>
     * @author Shane_Curcuru@lotus.com
     * @see #testFileInit(java.util.Properties)
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean postTestFileInit(Properties p)
    {

        reporter.logTraceMsg(
            "TestImpl.postTestFileInit() initialization complete");

        return true;
    }

    /**
     * Run all of our testcases.
     * Subclasses must override this method.  It should cause each testCase
     * in the test to be executed independently, and then return true if and
     * only if all testCases passed successfully.  If any testCase failed or
     * caused any unexpected errors, exceptions, etc., it should return false.
     * @author Shane_Curcuru@lotus.com
     *
     * NEEDSDOC @param p
     * @return true if all testCases passed, false otherwise
     */
    public boolean runTestCases(Properties p)
    {

        // @todo implement in your subclass
        reporter.logTraceMsg(
            "TestImpl.runTestCases() default implementation - please override");

        return true;
    }

    /**
     * Cleanup this test - called once after running testcases.
     * @author Shane_Curcuru@lotus.com
     *
     * NEEDSDOC @param p
     * @return true if cleanup successful, false otherwise
     */
    public boolean testFileClose(Properties p)
    {

        // Note: we don't want to use shortcut operators here,
        //       since we want each method to get called
        return preTestFileClose(p) & doTestFileClose(p)
               & postTestFileClose(p);
    }

    /**
     * Log a trace message - called once after running testcases.
     * <p>Predefined behavior - subclasses should <B>not</B> override this method.</p>
     * @todo currently is primarily here to mark that we're closing
     * the test, in case doTestFileClose() blows up somehow.  May not be needed.
     * @author Shane_Curcuru@lotus.com
     * @see #testFileClose()
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    protected boolean preTestFileClose(Properties p)
    {

        // Have the reporter log a trace that the test is about to cleanup
        reporter.logTraceMsg("TestImpl.preTestFileClose()");

        return true;
    }

    /**
     * Cleanup this test - called once after running testcases.
     * <p>Subclasses <b>must</b> override this to do whatever specific
     * processing they need to cleanup after all testcases are run.</p>
     * @author Shane_Curcuru@lotus.com
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    public boolean doTestFileClose(Properties p)
    {

        // @todo implement in your subclass
        reporter.logTraceMsg(
            "TestImpl.doTestFileClose() default implementation - please override");

        return true;
    }

    /**
     * Mark the test complete - called once after running testcases.
     * <p>Predefined behavior - subclasses should <b>not</b> override
     * this method. Currently just tells our reporter to log the
     * testFileClose. This will calculate final results, and complete
     * logging for any structured output logs (like XML files).</p>
     * @author Shane_Curcuru@lotus.com
     * @see #testFileClose()
     *
     * NEEDSDOC @param p
     *
     * NEEDSDOC ($objectName$) @return
     */
    protected boolean postTestFileClose(Properties p)
    {

        // Have the reporter log out our completion
        reporter.testFileClose();

        return true;
    }

    /**
     * Main method to run test from the command line.
     * Test subclasses <B>must</B> override, obviously.
     * @author Shane Curcuru
     *
     * NEEDSDOC @param args
     */
    public static void main(String[] args)
    {

        TestImpl app = new TestImpl();
        Properties p = new Properties();

        p.put(MAIN_CMDLINE, args);
        app.runTest(p);
    }
}  // end of class Test

