| /* |
| * 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.antunit; |
| |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| |
| /** |
| * 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 { |
| |
| /** |
| * name of the magic setUp target. |
| */ |
| private static final String SETUP = "setUp"; |
| |
| /** |
| * prefix that identifies test targets. |
| */ |
| private static final String TEST = "test"; |
| |
| /** |
| * name of the magic tearDown target. |
| */ |
| private static final String TEARDOWN = "tearDown"; |
| |
| /** |
| * name of the magic suiteSetUp target. |
| */ |
| private static final String SUITESETUP = "suiteSetUp"; |
| |
| /** |
| * name of the magic suiteTearDown target. |
| */ |
| private static final String SUITETEARDOWN = "suiteTearDown"; |
| |
| /** |
| * Object used to create projects in order to support test isolation. |
| */ |
| private final ProjectFactory prjFactory; |
| |
| /** |
| * Indicates if the startSuite method has been invoked. Use to fail fast if the |
| * the caller forget to call the startSuite method |
| */ |
| private boolean isSuiteStarted; |
| |
| /** |
| * Does that script have a setUp target (defined when scanning the script) |
| */ |
| private final boolean hasSetUp; |
| |
| /** |
| * Does that script have a tearDown target (defined when scanning the script) |
| */ |
| private final boolean hasTearDown; |
| |
| /** |
| * Does that script has a suiteSetUp target. |
| */ |
| private final boolean hasSuiteSetUp; |
| |
| /** |
| * Does that script has a suite tearDown target that should be executed. |
| */ |
| private final boolean hasSuiteTearDown; |
| |
| /** |
| * List of target names |
| */ |
| private final List testTargets; |
| |
| /** |
| * The project currently used. |
| */ |
| private Project project = null; |
| |
| /** |
| * Indicates if a target has already be executed using this project. |
| * Value is undefined when project is null. |
| */ |
| private boolean projectIsDirty; |
| |
| |
| /** |
| * Create a new AntScriptRunner on the given environment. |
| * @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. |
| * @throws BuildException The project can not be parsed |
| */ |
| public AntUnitScriptRunner(ProjectFactory prjFactory) throws BuildException { |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| * @throws BuildException The project can not be parsed |
| */ |
| public final Project getCurrentProject() throws BuildException { |
| //Method is final because it is called from the constructor |
| if (project == null) { |
| project = prjFactory.createProject(); |
| projectIsDirty = false; |
| } |
| return project; |
| } |
| |
| /** |
| * Get a project that has not yet been used in order to execute a target on it. |
| */ |
| private Project getCleanProject() { |
| if (project == null || projectIsDirty) { |
| 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. |
| projectIsDirty = true; |
| return project; |
| } |
| |
| /** |
| * @return List<String> List of test targets of the script file |
| */ |
| public List getTestTartgets() { |
| return testTargets; |
| } |
| |
| /** |
| * Provides the name of the active script. |
| */ |
| public String getName() { |
| 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. |
| */ |
| private boolean startSuite(AntUnitExecutionNotifier notifier) { |
| getCurrentProject().fireBuildStarted(); |
| if (hasSuiteSetUp) { |
| try { |
| Project newProject = getCleanProject(); |
| newProject.executeTarget(SUITESETUP); |
| } catch (BuildException e) { |
| notifier.fireStartTest(SUITESETUP); |
| fireFailOrError(SUITESETUP, e, notifier); |
| return false; |
| } |
| } |
| isSuiteStarted = true; //set to true only if suiteSetUp executed properly. |
| return true; |
| } |
| |
| /** |
| * Run the specific test target, possibly between the setUp and tearDown targets if |
| * 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 |
| */ |
| private void runTarget(String name, AntUnitExecutionNotifier notifier) { |
| if (!isSuiteStarted) { |
| throw new AssertionError(); |
| } |
| Project newProject = getCleanProject(); |
| Vector v = new Vector(); |
| if (hasSetUp) { |
| v.add(SETUP); |
| } |
| v.add(name); |
| // create and register a logcapturer on the newProject |
| LogCapturer lc = new LogCapturer(newProject); |
| try { |
| notifier.fireStartTest(name); |
| newProject.executeTargets(v); |
| } catch (BuildException 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. |
| notifier.fireEndTest(name); |
| // clean up |
| if (hasTearDown) { |
| try { |
| newProject.executeTarget(TEARDOWN); |
| } catch (final BuildException e) { |
| fireFailOrError(name, e, notifier); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Executes the suiteTearDown target if presents and report any execution error. |
| * @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. |
| */ |
| private void endSuite(Throwable caught, AntUnitExecutionNotifier notifier) { |
| if (hasSuiteTearDown) { |
| try { |
| Project newProject = getCleanProject(); |
| newProject.executeTarget(SUITETEARDOWN); |
| } catch (BuildException e) { |
| notifier.fireStartTest(SUITETEARDOWN); |
| fireFailOrError(SUITETEARDOWN, e, notifier); |
| } |
| } |
| getCurrentProject().fireBuildFinished(caught); |
| isSuiteStarted = false; |
| } |
| |
| /** |
| * Try to see whether the BuildException e is an AssertionFailedException |
| * 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, |
| AntUnitExecutionNotifier notifier) { |
| boolean failed = false; |
| Throwable t = e; |
| while (t != null && t instanceof BuildException) { |
| if (t instanceof AssertionFailedException) { |
| failed = true; |
| notifier.fireFail(targetName, (AssertionFailedException) t); |
| break; |
| } |
| t = ((BuildException) t).getCause(); |
| } |
| |
| if (!failed) { |
| 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); |
| } |
| } |
| |
| } |