CoverageTask.java

    1/*******************************************************************************
    2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
    3 * All rights reserved. This program and the accompanying materials
    4 * are made available under the terms of the Eclipse Public License v1.0
    5 * which accompanies this distribution, and is available at
    6 * http://www.eclipse.org/legal/epl-v10.html
    7 *
    8 * Contributors:
    9 *    Brock Janiczak - initial API and implementation
   10 *    
   11 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.ant;
   14
   15import static java.lang.String.format;
   16
   17import java.util.ArrayList;
   18import java.util.Collection;
   19
   20import org.apache.tools.ant.BuildException;
   21import org.apache.tools.ant.Project;
   22import org.apache.tools.ant.RuntimeConfigurable;
   23import org.apache.tools.ant.Task;
   24import org.apache.tools.ant.TaskContainer;
   25import org.apache.tools.ant.UnknownElement;
   26
   27/**
   28 * Container task to run Java/Junit tasks with the JaCoCo agent jar. Coverage
   29 * will only be applied if all of the following are true:
   30 * <ul>
   31 * <li>Exactly one sub task may be present</li>
   32 * <li>Task must be either Java or JUnit</li>
   33 * <li>Task must be using a forked VM (so vm args can be passed)</li>
   34 * </ul>
   35 * 
   36 * @ant.task category="java"
   37 * @author Brock Janiczak
   38 * @version $Revision: $
   39 */
   40public class CoverageTask extends AbstractCoverageTask implements TaskContainer {
   41
   42    private final Collection<TaskEnhancer> taskEnhancers = new ArrayList<TaskEnhancer>();
   43    private Task childTask;
   44
   45    /**
   46     * Creates a new default coverage task
   47     */
   48    public CoverageTask() {
   49        taskEnhancers.add(new JavaLikeTaskEnhancer("java"));
   50        taskEnhancers.add(new JavaLikeTaskEnhancer("junit"));
   51    }
   52
   53    /**
   54     * Add child task to this container and reconfigure it to run with coverage
   55     * enabled
   56     */
   57    public void addTask(final Task task) {
   58        if (childTask != null) {
   59            throw new BuildException(
   60                    "Only one child task can be supplied to the coverge task");
   61        }
   62
   63        this.childTask = task;
   64
   65        final UnknownElement unknownElement = (UnknownElement) task;
   66
   67        final String subTaskTypeName = unknownElement.getTaskType();
   68
   69        for (final TaskEnhancer enhancer : taskEnhancers) {
   70            if (enhancer.supportsTask(subTaskTypeName)) {
   71                enhancer.enhanceTask(task);
   72                return;
   73            }
   74        }
   75
   76        throw new BuildException(format(
   77                "%s is not a valid child of the coverage task.",
   78                subTaskTypeName));
   79
   80    }
   81
   82    /**
   83     * Executes subtask and performs any required cleanup
   84     */
   85    @Override
   86    public void execute() throws BuildException {
   87        if (childTask == null) {
   88            throw new BuildException(
   89                    "A child task must be supplied for the coverage task");
   90        }
   91        if (isEnabled()) {
   92            log(format("Enhancing %s with coverage.", childTask.getTaskName()));
   93            childTask.execute();
   94        }
   95    }
   96
   97    /**
   98     * Basic task enhancer that can handle all 'java like' tasks. That is, tasks
   99     * that have a top level fork attribute and nested jvmargs elements
  100     */
  101    private class JavaLikeTaskEnhancer implements TaskEnhancer {
  102
  103        private final String supportedTaskName;
  104
  105        public JavaLikeTaskEnhancer(final String supportedTaskName) {
  106            this.supportedTaskName = supportedTaskName;
  107        }
  108
  109        public boolean supportsTask(final String taskname) {
  110            return taskname.equals(supportedTaskName);
  111        }
  112
  113        public void enhanceTask(final Task task) {
  114            final RuntimeConfigurable configurableWrapper = task
  115                    .getRuntimeConfigurableWrapper();
  116
  117            final String forkValue = (String) configurableWrapper
  118                    .getAttributeMap().get("fork");
  119
  120            if (forkValue == null || !Project.toBoolean(forkValue)) {
  121                throw new BuildException(
  122                        "Coverage can only be applied on a forked VM");
  123            }
  124
  125            addJvmArgs((UnknownElement) task);
  126            task.maybeConfigure();
  127        }
  128
  129        public void addJvmArgs(final UnknownElement task) {
  130            final UnknownElement el = new UnknownElement("jvmarg");
  131            el.setTaskName("jvmarg");
  132            el.setQName("jvmarg");
  133
  134            final RuntimeConfigurable runtimeConfigurableWrapper = el
  135                    .getRuntimeConfigurableWrapper();
  136            runtimeConfigurableWrapper.setAttribute("value",
  137                    getLaunchingArgument());
  138
  139            task.getRuntimeConfigurableWrapper().addChild(
  140                    runtimeConfigurableWrapper);
  141
  142            task.addChild(el);
  143        }
  144    }
  145
  146    /**
  147     * The task enhancer is responsible for potentially reconfiguring a task to
  148     * support running with code coverage enabled
  149     */
  150    private interface TaskEnhancer {
  151        /**
  152         * @param taskname
  153         *            Task type to enhance
  154         * @return <code>true</code> iff this enhancer is capable of enhacing
  155         *         the requested task type
  156         */
  157        public boolean supportsTask(String taskname);
  158
  159        /**
  160         * Attempt to enhance the supplied task with coverage information. This
  161         * operation may fail if the task is being executed in the current VM
  162         * 
  163         * @param task
  164         *            Task instance to enhance (usually an
  165         *            {@link UnknownElement})
  166         * @throws BuildException
  167         *             Thrown if this enhancer can handle this type of task, but
  168         *             this instance can not be enhanced for some reason.
  169         */
  170        public void enhanceTask(Task task) throws BuildException;
  171    }
  172}