Refactoring : - Move active/inactive state managment from AntUnitScriptRunner to AntUnit - Split AntUnitExecutionPlatform into a ProjectFactory (required to parse the script) and AntUnitExceutionNotifier (required to run the script) - Move the invocation of the sequence suiteSetUp, test targets  suiteTearDown from AntUnit to AntUnitScriptRunner

git-svn-id: https://svn.apache.org/repos/asf/ant/antlibs/antunit/trunk@740452 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/org/apache/ant/antunit/AntUnit.java b/src/main/org/apache/ant/antunit/AntUnit.java
index c48d5f1..5bcadda 100644
--- a/src/main/org/apache/ant/antunit/AntUnit.java
+++ b/src/main/org/apache/ant/antunit/AntUnit.java
@@ -74,11 +74,7 @@
     private Union buildFiles;
 
     
-    private AntUnitExecutionPlatform myExecutionPlatform = new AntUnitExecutionPlatform() {
-
-        public Project createProjectForFile(File f) {
-            return AntUnit.this.createProjectForFile(f);
-        }
+    private AntUnitExecutionNotifier notifier = new AntUnitExecutionNotifier() {
 
         public void fireEndTest(String targetName) {
             AntUnit.this.fireEndTest(targetName);
@@ -100,9 +96,10 @@
     /**
      * The object responsible for the execution of the unit test.
      * scriptRunner is invoked to executes the targets and keep the
-     * reference to the project.
+     * reference to the project.  scriptRunner is defined only when the
+     * antunit script is running. 
      */
-    private AntUnitScriptRunner scriptRunner = new AntUnitScriptRunner(myExecutionPlatform);
+    private AntUnitScriptRunner scriptRunner;
 
     /**
      * listeners.
@@ -225,27 +222,19 @@
     /**
      * Processes a single build file.
      */
-    private void doFile(File f) {
+    private void doFile(final File f) {
         log("Running tests in build file " + f, Project.MSG_DEBUG);
-        scriptRunner.activate(f);
-        List testTargets = scriptRunner.scanFile();
-
-        // start test
-        Throwable caught = null;
+        ProjectFactory prjFactory = new ProjectFactory() {
+            public Project createProject() {
+                return createProjectForFile(f);
+            }            
+        };
         try {
-            if (!scriptRunner.startSuite()) {
-                return;
-            }
-            Iterator iter = testTargets.iterator();
-            while (iter.hasNext()) {
-                String name = (String) iter.next();
-                scriptRunner.runTarget(name);
-            }
-        } catch (Throwable e) {
-            caught = e;
+            scriptRunner = new AntUnitScriptRunner(prjFactory);
+            List testTargets = scriptRunner.getTestTartgets();
+            scriptRunner.runSuite(testTargets, notifier);
         } finally {
-            scriptRunner.endSuite(caught);            
-            scriptRunner.deactivate();
+            scriptRunner=null;
         }
     }
 
@@ -255,7 +244,7 @@
      * @param outputToHandle the output to handle.
      */
     public void handleOutput(String outputToHandle) {
-        if (scriptRunner.isActive()) {
+        if (scriptRunner!=null) {
             scriptRunner.getCurrentProject().demuxOutput(outputToHandle, false);
         } else {
             super.handleOutput(outputToHandle);
@@ -270,7 +259,7 @@
      */
     public int handleInput(byte[] buffer, int offset, int length)
         throws IOException {
-        if (scriptRunner.isActive()) {
+        if (scriptRunner!=null) {
             return scriptRunner.getCurrentProject().demuxInput(buffer, offset, length);
         }
         return super.handleInput(buffer, offset, length);
@@ -281,7 +270,7 @@
      * @param toFlush the output String to flush.
      */
     public void handleFlush(String toFlush) {
-        if (scriptRunner.isActive()) {
+        if (scriptRunner!=null) {
             scriptRunner.getCurrentProject().demuxFlush(toFlush, false);
         } else {
             super.handleFlush(toFlush);
@@ -293,7 +282,7 @@
      * @param errorOutputToHandle the error output to handle.
      */
     public void handleErrorOutput(String errorOutputToHandle) {
-        if (scriptRunner.isActive()) {
+        if (scriptRunner!=null) {
             scriptRunner.getCurrentProject().demuxOutput(errorOutputToHandle, true);
         } else {
             super.handleErrorOutput(errorOutputToHandle);
@@ -305,7 +294,7 @@
      * @param errorOutputToFlush the error output to flush.
      */
     public void handleErrorFlush(String errorOutputToFlush) {
-        if (scriptRunner.isActive()) {
+        if (scriptRunner!=null) {
             scriptRunner.getCurrentProject().demuxFlush(errorOutputToFlush, true);
         } else {
             super.handleErrorFlush(errorOutputToFlush);
diff --git a/src/main/org/apache/ant/antunit/AntUnitExecutionPlatform.java b/src/main/org/apache/ant/antunit/AntUnitExecutionNotifier.java
similarity index 83%
rename from src/main/org/apache/ant/antunit/AntUnitExecutionPlatform.java
rename to src/main/org/apache/ant/antunit/AntUnitExecutionNotifier.java
index 15f9270..a531766 100644
--- a/src/main/org/apache/ant/antunit/AntUnitExecutionPlatform.java
+++ b/src/main/org/apache/ant/antunit/AntUnitExecutionNotifier.java
@@ -25,16 +25,10 @@
 import org.apache.tools.ant.Project;
 
 /** 
- * Provides methods that allow the AntUnitScriptRunner to interact
- * with the environment in which it executes.
+ * Provides methods that allow the AntUnitScriptRunner to notify the test progress.
+ * @since 1.2
  */
-public interface AntUnitExecutionPlatform {
-
-    /**
-     * Creates a new project instance and configures it.
-     * @param f the File for which to create a Project.
-     */
-    public Project createProjectForFile(File f);
+public interface AntUnitExecutionNotifier {
 
     /**
      * invokes start on all registered test listeners.
diff --git a/src/main/org/apache/ant/antunit/AntUnitScriptRunner.java b/src/main/org/apache/ant/antunit/AntUnitScriptRunner.java
index cf207a3..3b9fe80 100644
--- a/src/main/org/apache/ant/antunit/AntUnitScriptRunner.java
+++ b/src/main/org/apache/ant/antunit/AntUnitScriptRunner.java
@@ -1,6 +1,5 @@
 package org.apache.ant.antunit;
 
-import java.io.File;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -11,17 +10,13 @@
 import org.apache.tools.ant.Project;
 
 /** 
- * Run antunit tests.  The lifecycle of this object is :
- * <ol>
- * <li> activate(file) : Indicates the runner that the given file should be used.</li>
- * <li> scanFile() : Provides you the list of targets.</li>
- * <li> startSuite() : Start the suite</li>
- * <li> runTarget(targetName) : Executed one or more time</li>
- * <li> endSuite() : End the suite</li>
- * <li> deactivate() Indicates to the runner that the test is finished and all 
- *      resources can be freed </li>
- * </ol>
- * Every step is mandatory. 
+ * Run antunit tests suites.  This AntUnitScriptRunner is responsible for the 
+ * management of the ant project and the correct invocation the target (taking 
+ * into account properly the [case]setUp and [case]tearDown targets).
+ * The user can however provide the order of the test targets and or can filter
+ * the list of test targets to execute.
+ * The user must also provide its ProjectFactory and an AntUnitExecutionNotifier.
+ * @since 1.2 
  */
 public class AntUnitScriptRunner {
 
@@ -51,24 +46,11 @@
     private static final String SUITETEARDOWN = "suiteTearDown";
 
     /**
-     * Object used to interact with the environment (for example an ant task or a junit runner)
+     * Object used to create projects in order to support test isolation.
      */
-    private final AntUnitExecutionPlatform env;
-
-    /**
-     * Ant script file currently under testing.  The file is set at activation, and used
-     * during all execution every time we want have to create a new project.</br>
-     * It is only defined when the project isActive()
-     */
-    private File scriptFile;
+    private final ProjectFactory prjFactory;
     
     /**
-     * Indicates if the active project is already scanned and if the value the fields
-     * hasSuiteSetUp, hasSetUp, hasTearDown, hasSuiteTearDown are defined. 
-     */
-    private boolean isScanned;
-
-    /**
      * Indicates if the startSuite method has been invoked.  Use to fail fast if the
      * the caller forget to call the startSuite method
      */
@@ -77,22 +59,27 @@
     /**
      * Does that script have a setUp target (defined when scanning the script)
      */
-    private boolean hasSetUp;
+    private final boolean hasSetUp;
 
     /**
      * Does that script have a tearDown target (defined when scanning the script)
      */
-    private boolean hasTearDown;
+    private final boolean hasTearDown;
 
     /**
      * Does that script has a suiteSetUp target.
      */
-    private boolean hasSuiteSetUp;
+    private final boolean hasSuiteSetUp;
 
     /**
      * Does that script has a suite tearDown target that should be executed.
      */
-    private boolean hasSuiteTearDown;
+    private final boolean hasSuiteTearDown;
+
+    /**
+     * List of target names
+     */
+    private final List testTargets;
 
     /** 
      * The project currently used.
@@ -108,54 +95,35 @@
     
     /**
      * Create a new AntScriptRunner on the given environment.
-     * @param env The environment used to create project and where the test progress will be 
-     * notified. 
+     * @param prjFactory A factory for the ant project that will contains the antunit test to execute.
+     * The factory might be invoked multiple time in order to provide test isolation.
      */
-    public AntUnitScriptRunner(AntUnitExecutionPlatform env) {
-        if (env == null) {
-            throw new AssertionError();
+    public AntUnitScriptRunner(ProjectFactory prjFactory) {
+        this.prjFactory = prjFactory;
+        Project newProject = getCurrentProject();
+        Map targets = newProject.getTargets();
+        hasSetUp = targets.containsKey(SETUP);
+        hasTearDown = targets.containsKey(TEARDOWN);
+        hasSuiteSetUp = targets.containsKey(SUITESETUP);
+        hasSuiteTearDown = targets.containsKey(SUITETEARDOWN);
+        testTargets = new LinkedList();
+        Iterator it = targets.keySet().iterator();
+        while (it.hasNext()) {
+            String name = (String) it.next();
+            if (name.startsWith(TEST) && !name.equals(TEST)) {
+                testTargets.add(name);
+            }
         }
-        this.env = env;
-    }
-
-    /** 
-     * Set the ant script to use.
-     * @post isActive() 
-     */
-    public void activate(File f) {
-        scriptFile = f;
-        project = null;
-        isScanned = false;
-        isSuiteStarted = false;
-    }
-
-    /** 
-     * Declare that the current ant script doesn't need to be used anymore.
-     * @post !isActive() 
-     */
-    public void deactivate() {
-        scriptFile = null;
-        project = null; 
-    }
-
-    /** 
-     * Indicates if there is a project currently under test. 
-     */
-    public boolean isActive() {
-        return scriptFile != null;
     }
 
     /**
      * Get the project currently in use.  The caller is not allowed to invoke a target or
      * do anything that would break the isolation of the test targets.
-     * @pre isActif()
      */
-    public Project getCurrentProject() {
-        if (!isActive()) {
-            throw new AssertionError();
-        }
+    public final Project getCurrentProject() {
+    	//Method is final because it is called from the constructor
         if (project == null) {
-            project = env.createProjectForFile(scriptFile);
+            project = prjFactory.createProject();
             projectIsDirty = false;
         }
         return project;
@@ -163,14 +131,10 @@
 
     /**
      * Get a project that has not yet been used in order to execute a target on it.
-     * @pre isActive()
      */
     private Project getCleanProject() {
-        if (!isActive()) {
-            throw new AssertionError();
-        }
         if (project == null || projectIsDirty) {
-            project = env.createProjectForFile(scriptFile);
+            project = prjFactory.createProject();
         }
         //we already set isDirty to true in order to make sure we didn't reuse
         //this project next time getCleanProject is called.  
@@ -179,60 +143,34 @@
     }
 
     /**
-     * Provides the list of test targets of the active antunit script.
-     * @pre isActive()
-     * @return List<String> List of test target names
+     * @return List<String> List of test targets of the script file
      */
-    public List scanFile() {
-        if (!isActive()) {
-            throw new AssertionError();
-        }
-        Project newProject = getCurrentProject();
-        Map targets = newProject.getTargets();
-        hasSetUp = targets.containsKey(SETUP);
-        hasTearDown = targets.containsKey(TEARDOWN);
-        hasSuiteSetUp = targets.containsKey(SUITESETUP);
-        hasSuiteTearDown = targets.containsKey(SUITETEARDOWN);
-        List testTargets = new LinkedList();
-        Iterator it = targets.keySet().iterator();
-        while (it.hasNext()) {
-            String name = (String) it.next();
-            if (name.startsWith(TEST) && !name.equals(TEST)) {
-                testTargets.add(name);
-            }
-        }
-        isScanned = true;
+    public List getTestTartgets() {
         return testTargets;
     }
 
     /**
      * Provides the name of the active script.
-     * @pre isAvtive()
      */
     public String getName() {
-        if (!isActive()) {
-            throw new AssertionError();
-        }
         return getCurrentProject().getName();
     }
 
     /**
      * Executes the suiteSetUp target if presents and report any execution error.
+     * A failure is reported to the notifier and by returning false.
      * Note that if the method return false, you are not allowed to run targets.
      * @return false in case of execution failure.  true in case of success. 
      */
-    public boolean startSuite() {
-        if (!isScanned) {
-            throw new AssertionError();
-        }
+    private boolean startSuite(AntUnitExecutionNotifier notifier) {
         getCurrentProject().fireBuildStarted();
         if (hasSuiteSetUp) {
             try {
                 Project newProject = getCleanProject();
                 newProject.executeTarget(SUITESETUP);
             } catch (BuildException e) {
-                env.fireStartTest(SUITESETUP);
-                fireFailOrError(SUITESETUP, e);
+                notifier.fireStartTest(SUITESETUP);
+                fireFailOrError(SUITESETUP, e, notifier);
                 return false;
             }
         }
@@ -242,10 +180,12 @@
 
     /** 
      * Run the specific test target, possibly between the setUp and tearDown targets if
-     * it exists.  Exception or failures are reported to the execution environment.
+     * it exists.  Exception or failures are reported to the notifier.
      * @param name name of the test target to execute.
+     * @param notifier will receive execution notifications.
+     * @pre startSuite has been invoked successfully
      */
-    public void runTarget(String name) {
+    private void runTarget(String name, AntUnitExecutionNotifier notifier) {
         if (!isSuiteStarted) {
             throw new AssertionError();
         }
@@ -258,23 +198,23 @@
         // create and register a logcapturer on the newProject
         LogCapturer lc = new LogCapturer(newProject);
         try {
-            env.fireStartTest(name);
+            notifier.fireStartTest(name);
             newProject.executeTargets(v);
         } catch (BuildException e) {
-            fireFailOrError(name, e);
+            fireFailOrError(name, e, notifier);
         } finally {
             // fire endTest here instead of the endTarget
             // event, otherwise an error would be
             // registered after the endTest event -
             // endTarget is called before this method's catch block
             // is reached.
-            env.fireEndTest(name);
+            notifier.fireEndTest(name);
             // clean up
             if (hasTearDown) {
                 try {
                     newProject.executeTarget(TEARDOWN);
                 } catch (final BuildException e) {
-                    fireFailOrError(name, e);
+                    fireFailOrError(name, e, notifier);
                 }
             }
         }
@@ -282,20 +222,18 @@
 
     /**
      * Executes the suiteTearDown target if presents and report any execution error.
-     * @param caught Any internal exception triggered (and catched) by the caller indicating that 
-     * the this runner could not be invoked as expected.  
+     * @param caught Any internal exception triggered (and caught) by the caller indicating that 
+     * the execution could not be invoked as expected.  
+     * @param notifier will receive execution notifications.
      */
-    public void endSuite(Throwable caught) {
-        if (!isScanned) {
-            throw new AssertionError();
-        }
+    private void endSuite(Throwable caught, AntUnitExecutionNotifier notifier) {
         if (hasSuiteTearDown) {
             try {
                 Project newProject = getCleanProject();
                 newProject.executeTarget(SUITETEARDOWN);
             } catch (BuildException e) {
-                env.fireStartTest(SUITETEARDOWN);
-                fireFailOrError(SUITETEARDOWN, e);
+                notifier.fireStartTest(SUITETEARDOWN);
+                fireFailOrError(SUITETEARDOWN, e, notifier);
             }
         }
         getCurrentProject().fireBuildFinished(caught);
@@ -307,20 +245,45 @@
      * or is caused by an AssertionFailedException. If so, fire a failure for 
      * given targetName.  Otherwise fire an error.
      */
-    private void fireFailOrError(String targetName, BuildException e) {
+    private void fireFailOrError(String targetName, BuildException e, 
+                                 AntUnitExecutionNotifier notifier) {
         boolean failed = false;
         Throwable t = e;
         while (t != null && t instanceof BuildException) {
             if (t instanceof AssertionFailedException) {
                 failed = true;
-                env.fireFail(targetName, (AssertionFailedException) t);
+                notifier.fireFail(targetName, (AssertionFailedException) t);
                 break;
             }
             t = ((BuildException) t).getCause();
         }
 
         if (!failed) {
-            env.fireError(targetName, e);
+            notifier.fireError(targetName, e);
+        }
+    }
+
+
+    /**
+     * Executes the suite.
+     * @param suiteTargets An ordered list of test targets.  It must be a sublist of getTestTargets
+     * @param notifier 
+     */
+    public void runSuite(List suiteTargets, AntUnitExecutionNotifier notifier) {
+        Throwable caught = null;
+        try {
+            if (!startSuite(notifier)) {
+                return;
+            }
+            Iterator iter = suiteTargets.iterator();
+            while (iter.hasNext()) {
+                String name = (String) iter.next();
+                runTarget(name, notifier);
+            }
+        } catch (Throwable e) {
+            caught = e;
+        } finally {
+            endSuite(caught, notifier);
         }
     }
 
diff --git a/src/main/org/apache/ant/antunit/ProjectFactory.java b/src/main/org/apache/ant/antunit/ProjectFactory.java
new file mode 100644
index 0000000..a056c76
--- /dev/null
+++ b/src/main/org/apache/ant/antunit/ProjectFactory.java
@@ -0,0 +1,20 @@
+package org.apache.ant.antunit;
+
+import org.apache.tools.ant.Project;
+
+/** 
+ * Provides project instances for AntUnit execution.<br/>  
+ * The aproach to creates a project depends on the context.  When invoked from an 
+ * ant project, some elements might be intialized from the parent project.  When
+ * executed in a junit runner, a brand new project must be initialized.<br/>
+ * The AntScriptRunner will usually creates multiple project in order to provide test isolation. 
+ * @since 1.2
+ */
+public interface ProjectFactory {
+    
+    /**
+     * Creates a new project instance and configures it according to the execution context.
+     */
+    public Project createProject();
+
+}