You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by jh...@apache.org on 2007/12/20 14:41:26 UTC

svn commit: r605925 - in /ant/sandbox/parallelexecutor: ./ src/ src/main/ src/main/org/ src/main/org/apache/ src/main/org/apache/ant/ src/main/org/apache/ant/parallelexecutor/ src/test/ src/test/org/ src/test/org/apache/ src/test/org/apache/ant/ src/te...

Author: jhm
Date: Thu Dec 20 05:41:22 2007
New Revision: 605925

URL: http://svn.apache.org/viewvc?rev=605925&view=rev
Log:
Add current working version of the parallel executor for backup and discussion.
Current state:
* (seems to) work from command line
* JUnit test failes

Added:
    ant/sandbox/parallelexecutor/
    ant/sandbox/parallelexecutor/build.xml
    ant/sandbox/parallelexecutor/src/
    ant/sandbox/parallelexecutor/src/main/
    ant/sandbox/parallelexecutor/src/main/org/
    ant/sandbox/parallelexecutor/src/main/org/apache/
    ant/sandbox/parallelexecutor/src/main/org/apache/ant/
    ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/
    ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/ParallelExecutor.java
    ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainer.java
    ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainerStatus.java
    ant/sandbox/parallelexecutor/src/test/
    ant/sandbox/parallelexecutor/src/test/org/
    ant/sandbox/parallelexecutor/src/test/org/apache/
    ant/sandbox/parallelexecutor/src/test/org/apache/ant/
    ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/
    ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/ParallelExecutorTest.java
    ant/sandbox/parallelexecutor/src/testdata/
    ant/sandbox/parallelexecutor/src/testdata/double-deps.xml
    ant/sandbox/parallelexecutor/src/testdata/failures.xml
    ant/sandbox/parallelexecutor/src/testdata/multiple-deps.xml
    ant/sandbox/parallelexecutor/src/testdata/simple-dep.xml
    ant/sandbox/parallelexecutor/src/testdata/simple.xml

Added: ant/sandbox/parallelexecutor/build.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/build.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/build.xml (added)
+++ ant/sandbox/parallelexecutor/build.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,70 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project name="ant-parallelexecutor" default="build">
+    <property name="javac.source" value="1.6"/>
+    <property name="javac.target" value="1.6"/>
+    
+    <property name="build.dir" value="build"/>
+    <property name="src.dir"   value="src"/>
+    <property name="version"   value="1.0"/>
+    
+    <property name="main.src.dir"  value="${src.dir}/main"/>
+    <property name="junit.src.dir" value="${src.dir}/test"/>
+    <property name="data.src.dir"  value="${src.dir}/testdata"/>
+    
+    <property name="main.classes.dir"  value="${build.dir}/main"/>
+    <property name="junit.classes.dir" value="${build.dir}/test"/>
+    <property name="data.classes.dir"  value="${build.dir}/testdata"/>
+
+
+    <target name="clean">
+        <delete dir="${build.dir}"/>
+    </target>
+
+
+    <target name="compile">
+        <mkdir dir="${main.classes.dir}" />
+        <javac srcdir="${main.src.dir}"
+               destdir="${main.classes.dir}"
+               source="${javac.source}"
+               target="${javac.target}"
+              debug="true"
+        />
+    </target>
+
+
+    <target name="jar" depends="compile">
+        <jar destfile="${build.dir}/${ant.project.name}.jar">
+            <fileset dir="${main.classes.dir}"/>
+            <manifest>
+              <attribute name="Built-By" value="${user.name}"/>
+              <attribute name="Extension-name"         value="org.apache.ant.parallelexecutor"/>
+              <attribute name="Specification-Title"    value="Apache Ant"/>
+              <attribute name="Specification-Version"  value="${version}"/>
+              <attribute name="Specification-Vendor"   value="Apache Software Foundation"/>
+              <attribute name="Implementation-Title"   value="org.apache.ant.parallelexecutor"/>
+              <attribute name="Implementation-Version" value="${version}"/>
+              <attribute name="Implementation-Vendor"  value="Apache Software Foundation"/>
+            </manifest>
+        </jar>
+    </target>
+
+
+    <target name="build" depends="jar" />
+
+
+</project>
\ No newline at end of file

Added: ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/ParallelExecutor.java
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/ParallelExecutor.java?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/ParallelExecutor.java (added)
+++ ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/ParallelExecutor.java Thu Dec 20 05:41:22 2007
@@ -0,0 +1,255 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ant.parallelexecutor;
+
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.WAITING;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Executor;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Target;
+
+/** 
+ * <p>This executors parallelizes the exution of Ant targets.
+ * Each target will run in its own thread. That thread will be started
+ * if all dependend targets are finished, the if/unless attributes are
+ * evaluated and no dependend target failed. When a TargetContainer finishes
+ * it calls this Executors <tt>targetFinished</tt> so that the Executor could
+ * restart all the waiting threads.</p>
+ * <p>This executor is used via Ants magic property <tt>ant.executor.class</tt></p>
+ * <pre>
+ *   ant 
+ *     -Dant.executor.class=org.apache.ant.parallelexecutor.ParallelExecutor
+ *     -lib pathToJarContainingTheExecutor
+ * </pre>
+ */
+public class ParallelExecutor implements Executor {
+    
+    /** 
+     * Default value for waiting for shutting down the ExecutorService.
+     * @see ExecutorService#awaitTermination(long, TimeUnit)
+     * @see #EXECUTOR_SERVICE_SHUTDOWN_TIME_UNIT
+     */
+    public static final long EXECUTOR_SERVICE_SHUTDOWN_TIME_VALUE = 10;
+
+    /**
+     * TimeUnit for the shutdown time.
+     */
+    public static final TimeUnit EXECUTOR_SERVICE_SHUTDOWN_TIME_UNIT = TimeUnit.MINUTES;
+    
+    
+    /**
+     * Targets which should run, wrapped by TargetContainers for threading and monitoring.
+     */
+    private Set<TargetContainer> targetsToProcess;
+
+    
+    /**
+     * ExecutorService for Thread-creation.
+     */
+    private ExecutorService executorService;
+
+
+    /**
+     * Entry-point defined in the Executor interface.
+     * Initializes this Executor and starts the threads.
+     * @param project Ant-project which hold the targets
+     * @param targetNames target names as specified on the command line to execute 
+     * @see org.apache.tools.ant.Executor#executeTargets(org.apache.tools.ant.Project, java.lang.String[])
+     */
+    @Override
+    public void executeTargets(Project project, String[] targetNames) throws BuildException {
+        targetsToProcess = getTargetsToProcess(project, targetNames);
+        executorService = java.util.concurrent.Executors.newCachedThreadPool();
+        startWaitingContainers();
+        while (unfinishedTargets()) {
+            sleep(50);
+        }
+        sleep(100);
+        buildFinished();
+    }
+
+
+    /**
+     * Lets the current thread sleep for a given time.
+     * @param timeMS sleep time in milliseconds
+     */
+    protected void sleep(long timeMS) {
+        try {
+            Thread.sleep(timeMS);
+        } catch (InterruptedException e) {
+            // no-op
+        }
+    }
+    
+    
+    /**
+     * Initializes the list of TargetContainers with all targets which should be started.
+     * @param project      project containing the targets
+     * @param targetNames  list of the targets to start
+     * @return             list of TargetContainers for these targets
+     */
+    private Set<TargetContainer> getTargetsToProcess(Project project, String[] targetNames) {
+        Set<TargetContainer> rv = new HashSet<TargetContainer>();
+        // iterate over all required targets - including dependent targets
+        for (Object targetName : project.topoSort(targetNames, project.getTargets(), false)) {
+            // must be a String - Objects are not equal while iterating
+            String key = targetName.toString();
+            Target target = (Target)project.getTargets().get(key);
+            TargetContainer container = new TargetContainer(target, this);
+            rv.add(container);
+        }
+        return rv;
+    } 
+    
+    
+    /**
+     * not used
+     * @see org.apache.tools.ant.Executor#getSubProjectExecutor()
+     */
+    public Executor getSubProjectExecutor() {
+        return null;
+    }
+    
+    
+    /**
+     * Starts all waiting TargetContainers.
+     * @return <tt>true</tt> if one or more containers were be started, <tt>false</tt> otherwise.
+     */
+    private boolean startWaitingContainers() {
+        boolean hasStartedAContainer = false;
+        for (TargetContainer container : targetsToProcess) {
+            if (container.getCurrentStatus() == WAITING) {
+                container.start();
+                executorService.execute(container);
+                hasStartedAContainer = true;
+            }
+        }
+        return hasStartedAContainer;
+    }
+
+
+    /**
+     * Call-back method for finishing TargetContainers.
+     * 
+     * @param container
+     *            TargetContainer which finished.
+     */
+    public void targetFinished(TargetContainer container) {
+        if (unfinishedTargets()) {
+            startWaitingContainers();
+        }
+    }
+
+
+    /**
+     * Checks if there are targets which haven't finished.
+     * @return result of the check
+     */
+    protected synchronized boolean unfinishedTargets() {
+        for (TargetContainer container : targetsToProcess) {
+            if (!container.getCurrentStatus().hasFinished()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Collects all the results from the different targets.
+     */
+    private void buildFinished() {
+        // we have already checked that all started thread have completed
+        executorService.shutdown();
+
+        // Checks the targets for BuildExceptions and their state.
+        List<BuildException> thrownExceptions = new ArrayList<BuildException>();
+        for (TargetContainer container : targetsToProcess) {
+            if (container.getBuildExeption()!=null) { 
+                thrownExceptions.add(container.getBuildExeption());
+            }
+        }
+        
+        // throw BuildExceptions if needed
+        throwCaughtExceptions(thrownExceptions);
+    }
+
+
+    /**
+     * Checks the given list of BuildExceptions and <ol>
+     * <li>throws a composite BuildException with the information provided by that list</li>
+     * <li>throws the BuildException if the list does contain only one exception</li>
+     * <li>does not throw any Exception if the list is empty</li>
+     * </ul>
+     * @param thrownExceptions list of caught exceptions
+     */
+    private void throwCaughtExceptions(List<BuildException> thrownExceptions) {
+        if (thrownExceptions.isEmpty()) {
+            return;
+        }
+        if (thrownExceptions.size() == 1) {
+            throw thrownExceptions.get(0);
+        }
+        // Collect all BEs into one new 
+        StringBuilder sb = new StringBuilder();
+        sb.append("Multiple BuildExceptions occured:")
+          .append(System.getProperty("line.separator"));
+        for (BuildException be : thrownExceptions) {
+            sb.append("\t")
+              .append(be.getLocation())
+              .append(" : ")
+              .append(be.getMessage())
+              .append(System.getProperty("line.separator"));
+        }
+        throw new BuildException(sb.toString());
+    }
+
+
+    /**
+     * Returns the current status for a given target.
+     * @param depName name of the target
+     * @return status of that target
+     */
+    public TargetContainerStatus getStatus(String depName) {
+        return getContainer(depName).getCurrentStatus();
+    }
+    
+    
+    /**
+     * Returns a TargetContainer by its name.
+     * @param targetName name of the target to look for
+     * @return the target container wrapping that target
+     */
+    private TargetContainer getContainer(String targetName) {
+        for (TargetContainer container : targetsToProcess) {
+            if (container.getName().equals(targetName)) {
+                return container;
+            }
+        }
+        return null;
+    }
+    
+}

Added: ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainer.java
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainer.java?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainer.java (added)
+++ ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainer.java Thu Dec 20 05:41:22 2007
@@ -0,0 +1,183 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ant.parallelexecutor;
+
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.CHECKING;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.FAILED;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.PREREQUISITE_FAILED;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.RUNNING;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.STARTING;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.SUCCESSED;
+import static org.apache.ant.parallelexecutor.TargetContainerStatus.WAITING;
+
+import java.util.Enumeration;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Target;
+
+
+/**
+ * The TargetContainer wrapps a target for the use in a multithreaded environment.
+ * It provides the "management methods" needed by the ParallelExecutor.
+ */
+public class TargetContainer implements Runnable {
+    
+    /** Current status of the target. */
+    private TargetContainerStatus currentStatus = WAITING;
+    
+    /** Wrapped target. */
+    private Target target;
+    
+    /** Caught exception if the target throws one. */
+    private BuildException caughtBuildExeption;
+    
+    /** The calling ParallelExecutor to inform on finished works. */
+    private ParallelExecutor caller;
+
+    
+    /**
+     * Constructor.
+     * @param target target to wrap
+     * @param caller calling exectuor for call back
+     */
+    public TargetContainer(Target target, ParallelExecutor caller) {
+        this.target = target;
+        this.caller = caller;
+        setCurrentStatus(WAITING);
+    }
+
+    
+    /**
+     * Gets the current status of the TargetContainer.
+     * @return status
+     */
+    public TargetContainerStatus getCurrentStatus() {
+        return currentStatus;
+    }
+
+    
+    /**
+     * Sets the current status of the TargetContainer.
+     * @param currentStatus new status
+     */
+    private void setCurrentStatus(TargetContainerStatus currentStatus) {
+        synchronized (this.currentStatus) {
+            this.currentStatus = currentStatus;
+        }
+    }
+
+
+    /**
+     * Called by the {@link ParallelExecutor} if the target should try
+     * to start.
+     * @see java.lang.Runnable#run()
+     */
+    @Override
+    public void run() {
+        setCurrentStatus(CHECKING);
+        TargetContainerStatus statusDepends = checkDependentTargets();
+        if (statusDepends == SUCCESSED) {
+            if (evaluateIf() && !evaluateUnless()) {
+                setCurrentStatus(RUNNING);
+                try {
+                    target.execute();
+                    setCurrentStatus(SUCCESSED);
+                } catch (BuildException be) {
+                    // Don't handle the be, just store it for handling by the executor.
+                    caughtBuildExeption = be;
+                    setCurrentStatus(FAILED);
+                }
+            } else {
+                // 'if' or 'unless' attributes failed
+                setCurrentStatus(PREREQUISITE_FAILED);
+            }
+        } else {
+            // Finishing the run method stops the thread. Status here is WAITING or PREFAILED
+            setCurrentStatus(statusDepends);
+        }
+        caller.targetFinished(this);
+    }
+    
+
+    /**
+     * Checks the result of the dependend targets. 
+     * @return <tt>SUCCESSED</tt> if <b>all</b> targets finished successfully,
+     *         <tt>PREREQUISITE_FAILED</tt> if one or more failed or
+     *         <tt>WAITING</tt> otherwise. 
+     */
+    @SuppressWarnings("unchecked")
+    private TargetContainerStatus checkDependentTargets() {
+        for (Enumeration deps = target.getDependencies(); deps.hasMoreElements();) {
+            TargetContainerStatus status = caller.getStatus((String) deps.nextElement());
+            if (status == FAILED || status == PREREQUISITE_FAILED) {
+                return PREREQUISITE_FAILED;
+            }
+            if (status != SUCCESSED) {
+                return WAITING;
+            }
+        }
+        return SUCCESSED; 
+    }
+
+
+    /**
+     * Checks if the property specified as <tt>unless</tt> is set.
+     * @return <tt>true</tt> if set, <tt>false</tt> otherwise
+     */
+    private boolean evaluateUnless() {
+        return target.getProject().getProperty(target.getUnless()) != null;
+    }
+
+
+    /**
+     * Checks if the property specified as <tt>if</tt> is set.
+     * @return <tt>true</tt> if set, <tt>false</tt> otherwise
+     */
+    private boolean evaluateIf() {
+        String ifName = target.getIf();
+        return ifName == null || target.getProject().getProperty(ifName) != null;
+    }
+
+
+    /**
+     * Gets the name of the target.
+     * @return target name
+     * @see Target#getName()
+     */
+    public String getName() {
+        return target.getName();
+    }
+
+    
+    /**
+     * Returns the caught exception thrown by the target. 
+     * @return the exception or <tt>null</tt>
+     */
+    public BuildException getBuildExeption() {
+        return caughtBuildExeption;
+    }
+
+    
+    /**
+     * Marks this container as starting.
+     */
+    public void start(){
+        setCurrentStatus(STARTING);
+    }
+    
+}

Added: ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainerStatus.java
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainerStatus.java?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainerStatus.java (added)
+++ ant/sandbox/parallelexecutor/src/main/org/apache/ant/parallelexecutor/TargetContainerStatus.java Thu Dec 20 05:41:22 2007
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ant.parallelexecutor;
+
+/**
+ * TargetContainer can be in the here defined states.
+ * The transistion between them is:<pre>
+ *   init --> WAITING
+ *   WAITING --> STARTING
+ *   STARTING --> CHECKING
+ *   CHECKING --> RUNNING, PREREQUISITE_FAILED
+ *   RUNNING --> SUCCESSED, FAILED
+ *   SUCCESSED --> end
+ *   FAILED --> end
+ *   PREREQUISITE_FAILED --> end
+ * </pre>
+ */
+public enum TargetContainerStatus {
+    /* Waiting for starting by the Executor. */
+    WAITING,
+    /* This state is directly set by the executor for marking as 'not-waiting'. */
+    STARTING,
+    /* Checking prerequisites (depends, if, unless). */
+    CHECKING,
+    /* Target is currently running. */
+    RUNNING,
+    /* Target has finished without any error. */
+    SUCCESSED,
+    /* Target has thrown a BuildException. */
+    FAILED,
+    /* Target could not run because a prerequisite has not succeeded. */
+    PREREQUISITE_FAILED;
+    
+    
+    /**
+     * Utility method for easier access to a set of states.
+     * <pre>Finished = [SUCCESSED|FAILED|PREREQUISITE_FAILED]</pre>
+     * @return computed state
+     */
+    public boolean hasFinished() {
+        return this == SUCCESSED || this == FAILED || this == PREREQUISITE_FAILED;
+    }
+}

Added: ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/ParallelExecutorTest.java
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/ParallelExecutorTest.java?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/ParallelExecutorTest.java (added)
+++ ant/sandbox/parallelexecutor/src/test/org/apache/ant/parallelexecutor/ParallelExecutorTest.java Thu Dec 20 05:41:22 2007
@@ -0,0 +1,142 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.ant.parallelexecutor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import junit.framework.TestCase;
+
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+
+/**
+ * Test class for the ParallelExecutor.
+ */
+public class ParallelExecutorTest extends TestCase {
+    
+    /**
+     * Runs the project and returns the order of the executed targets.
+     * @param pathToBuildfile relative path to the buildfile
+     * @param arguments additional arguments to pass to Ant
+     * @return the configured Ant project
+     */
+    @SuppressWarnings("unchecked")
+    protected List<String> runProject(String pathToBuildfile, String targets) {
+        Vector targetNames = new Vector();
+        for (String target : targets.split(" ")) {
+            targetNames.add(target);
+        }
+        Project project = new Project();
+        project.init();
+        File antFile = new File(System.getProperty("root"), pathToBuildfile);
+        project.setUserProperty("ant.file" , antFile.getAbsolutePath());
+        project.setUserProperty("ant.executor.class", "org.apache.ant.parallelexecutor.ParallelExecutor");
+        BuildOrderListener orderListener = new BuildOrderListener();
+        project.addBuildListener(orderListener);
+        ProjectHelper.configureProject(project, antFile);
+        project.executeTargets(targetNames);
+        return orderListener.getOrder();
+    }
+
+    
+    /**
+     * BuildListener which collects the order of executed targets.
+     */
+    class BuildOrderListener implements BuildListener {
+        List<String> order = new ArrayList<String>();
+        public List<String> getOrder() {
+            return order;
+        }
+        public void targetStarted(BuildEvent event) {
+            order.add(event.getTarget().getName());
+        }
+        // methods we dont need here
+        public void buildFinished(BuildEvent event) {}
+        public void buildStarted(BuildEvent event) {}
+        public void messageLogged(BuildEvent event) {}
+        public void targetFinished(BuildEvent event) {}
+        public void taskFinished(BuildEvent event) {}
+        public void taskStarted(BuildEvent event) {}
+    }
+    
+    
+    /**
+     * Converts a string-list into a string for easier comparison without
+     * any separation string.
+     * @param list input
+     * @return converted list
+     */
+    protected String list2String(List<String> list) {
+        StringBuilder sb = new StringBuilder();
+        for (String string : list) {
+            sb.append(string);
+        }
+        return sb.toString();
+    }
+    
+    
+    public void testOneTarget() {
+        String result = list2String(runProject("src/testdata/simple.xml", "A"));
+        assertEquals("A", result);
+    }
+    
+    public void testDependsOn1Target() {
+        String result = list2String(runProject("src/testdata/simple-dep.xml", "A"));
+        assertEquals("BA", result);
+    }
+
+    public void testDependsOn2Targets() {
+        String result = list2String(runProject("src/testdata/double-deps.xml", "A"));
+        assertTrue("Wrong order: " + result, result.equals("BCA") || result.equals("CBA") );
+    }
+    
+    public void testDependsOn2and1Targets() {
+        String result = list2String(runProject("src/testdata/multiple-deps.xml", "A"));
+        assertTrue("Wrong order: " + result,  result.equals("DBCA") || result.equals("DCBA") );
+    }
+    
+    public void testFailingTarget1() {
+        String result = list2String(runProject("src/testdata/failures.xml", "A"));
+        assertEquals("A", result);
+        //TODO: check that A throws a BuildException
+    }
+    
+    public void testFailingTarget2() {
+        String result = list2String(runProject("src/testdata/failures.xml", "B"));
+        assertEquals("C", result);
+        //TODO: check that C throws a BuildException
+    }
+
+    public void testFailingTarget3() {
+        String result = list2String(runProject("src/testdata/failures.xml", "D"));
+        assertEquals("ED", result);
+        //TODO: check that D throws a BuildException
+    }
+
+    public void testFailingTarget4() {
+        String result = list2String(runProject("src/testdata/failures.xml", "F"));
+        assertTrue("Wrong order: " + result,  result.equals("GH") || result.equals("HG") );
+        //TODO: check that G and H throw a BuildException
+    }
+
+}

Added: ant/sandbox/parallelexecutor/src/testdata/double-deps.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/testdata/double-deps.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/testdata/double-deps.xml (added)
+++ ant/sandbox/parallelexecutor/src/testdata/double-deps.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,5 @@
+<project>
+    <target name="A" depends="B,C"/> 
+    <target name="B"/>
+    <target name="C"/>
+</project>
\ No newline at end of file

Added: ant/sandbox/parallelexecutor/src/testdata/failures.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/testdata/failures.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/testdata/failures.xml (added)
+++ ant/sandbox/parallelexecutor/src/testdata/failures.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,36 @@
+<project>
+
+    <target name="A">
+        <echo>A</echo>
+        <fail message="A failed"/>
+    </target>
+    
+    <target name="B" depends="C">
+        <echo>B</echo>
+    </target>
+    <target name="C">
+        <echo>C</echo>
+        <faill message="C failed"/>
+    </target>
+    
+    <target name="D" depends="E">
+        <echo>D</echo>
+        <faill message="D failed"/>
+    </target>
+    <target name="E">
+        <echo>E</echo>
+    </target>
+
+    <target name="F" depends="G,H">
+        <echo>F</echo>
+    </target>
+    <target name="G">
+        <echo>G</echo>
+        <faill message="G failed"/>
+    </target>
+    <target name="H">
+        <echo>H</echo>
+        <faill message="H failed"/>
+    </target>
+
+</project>
\ No newline at end of file

Added: ant/sandbox/parallelexecutor/src/testdata/multiple-deps.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/testdata/multiple-deps.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/testdata/multiple-deps.xml (added)
+++ ant/sandbox/parallelexecutor/src/testdata/multiple-deps.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,6 @@
+<project>
+    <target name="A" depends="B,C"/>
+    <target name="B" depends="D"/>
+    <target name="C" depends="D"/>
+    <target name="D"/>
+</project>
\ No newline at end of file

Added: ant/sandbox/parallelexecutor/src/testdata/simple-dep.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/testdata/simple-dep.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/testdata/simple-dep.xml (added)
+++ ant/sandbox/parallelexecutor/src/testdata/simple-dep.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,4 @@
+<project>
+    <target name="A" depends="B"/>
+    <target name="B"/>
+</project>
\ No newline at end of file

Added: ant/sandbox/parallelexecutor/src/testdata/simple.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/parallelexecutor/src/testdata/simple.xml?rev=605925&view=auto
==============================================================================
--- ant/sandbox/parallelexecutor/src/testdata/simple.xml (added)
+++ ant/sandbox/parallelexecutor/src/testdata/simple.xml Thu Dec 20 05:41:22 2007
@@ -0,0 +1,4 @@
+<project>
+    <target name="A"/>
+    <target name="B"/>
+</project>
\ No newline at end of file