1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors 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 * Evgeny Mandrikov - TestNG support 10 * Brock Janiczak - initial API and implementation 11 * 12 *******************************************************************************/ 13 package org.jacoco.ant; 14 15 import static java.lang.String.format; 16 17 import java.util.ArrayList; 18 import java.util.Collection; 19 20 import org.apache.tools.ant.BuildException; 21 import org.apache.tools.ant.Project; 22 import org.apache.tools.ant.RuntimeConfigurable; 23 import org.apache.tools.ant.Task; 24 import org.apache.tools.ant.TaskContainer; 25 import 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 public class CoverageTask extends AbstractCoverageTask implements TaskContainer { 37 38 private final Collection<TaskEnhancer> taskEnhancers = new ArrayList<TaskEnhancer>(); 39 private Task childTask; 40 41 /** 42 * Creates a new default coverage task 43 */ CoverageTask()44 public CoverageTask() { 45 super(); 46 taskEnhancers.add(new JavaLikeTaskEnhancer("java")); 47 taskEnhancers.add(new JavaLikeTaskEnhancer("junit")); 48 taskEnhancers.add(new TestNGTaskEnhancer("testng")); 49 } 50 51 /** 52 * Add child task to this container and reconfigure it to run with coverage 53 * enabled 54 */ addTask(final Task task)55 public void addTask(final Task task) { 56 if (childTask != null) { 57 throw new BuildException( 58 "Only one child task can be supplied to the coverge task", 59 getLocation()); 60 } 61 62 this.childTask = task; 63 64 final String subTaskTypeName = task.getTaskType(); 65 66 final TaskEnhancer enhancer = findEnhancerForTask(subTaskTypeName); 67 if (enhancer == null) { 68 throw new BuildException(format( 69 "%s is not a valid child of the coverage task", 70 subTaskTypeName), getLocation()); 71 } 72 73 if (isEnabled()) { 74 log(format("Enhancing %s with coverage", childTask.getTaskName())); 75 enhancer.enhanceTask(task); 76 } 77 78 task.maybeConfigure(); 79 } 80 findEnhancerForTask(final String taskName)81 private TaskEnhancer findEnhancerForTask(final String taskName) { 82 for (final TaskEnhancer enhancer : taskEnhancers) { 83 if (enhancer.supportsTask(taskName)) { 84 return enhancer; 85 } 86 } 87 88 return null; 89 } 90 91 /** 92 * Executes subtask and performs any required cleanup 93 */ 94 @Override execute()95 public void execute() throws BuildException { 96 if (childTask == null) { 97 throw new BuildException( 98 "A child task must be supplied for the coverage task", 99 getLocation()); 100 } 101 102 childTask.execute(); 103 } 104 105 /** 106 * Task enhancer for TestNG. TestNG task always run in a forked VM and has 107 * nested jvmargs elements 108 */ 109 private class TestNGTaskEnhancer extends JavaLikeTaskEnhancer { 110 TestNGTaskEnhancer(final String supportedTaskName)111 public TestNGTaskEnhancer(final String supportedTaskName) { 112 super(supportedTaskName); 113 } 114 115 @Override enhanceTask(final Task task)116 public void enhanceTask(final Task task) { 117 addJvmArgs(task); 118 } 119 120 } 121 122 /** 123 * Basic task enhancer that can handle all 'java like' tasks. That is, tasks 124 * that have a top level fork attribute and nested jvmargs elements 125 */ 126 private class JavaLikeTaskEnhancer implements TaskEnhancer { 127 128 private final String supportedTaskName; 129 JavaLikeTaskEnhancer(final String supportedTaskName)130 public JavaLikeTaskEnhancer(final String supportedTaskName) { 131 this.supportedTaskName = supportedTaskName; 132 } 133 supportsTask(final String taskname)134 public boolean supportsTask(final String taskname) { 135 return taskname.equals(supportedTaskName); 136 } 137 enhanceTask(final Task task)138 public void enhanceTask(final Task task) { 139 final RuntimeConfigurable configurableWrapper = task 140 .getRuntimeConfigurableWrapper(); 141 142 final String forkValue = getProject().replaceProperties( 143 (String) configurableWrapper.getAttributeMap().get("fork")); 144 145 if (!Project.toBoolean(forkValue)) { 146 throw new BuildException( 147 "Coverage can only be applied on a forked VM", 148 getLocation()); 149 } 150 151 addJvmArgs(task); 152 } 153 addJvmArgs(final Task task)154 public void addJvmArgs(final Task task) { 155 final UnknownElement el = new UnknownElement("jvmarg"); 156 el.setTaskName("jvmarg"); 157 el.setQName("jvmarg"); 158 159 final RuntimeConfigurable runtimeConfigurableWrapper = el 160 .getRuntimeConfigurableWrapper(); 161 runtimeConfigurableWrapper.setAttribute("value", 162 getLaunchingArgument()); 163 164 task.getRuntimeConfigurableWrapper().addChild( 165 runtimeConfigurableWrapper); 166 167 ((UnknownElement) task).addChild(el); 168 } 169 } 170 171 /** 172 * The task enhancer is responsible for potentially reconfiguring a task to 173 * support running with code coverage enabled 174 */ 175 private interface TaskEnhancer { 176 /** 177 * @param taskname 178 * Task type to enhance 179 * @return <code>true</code> if this enhancer is capable of enhancing 180 * the requested task type 181 */ supportsTask(String taskname)182 public boolean supportsTask(String taskname); 183 184 /** 185 * Attempt to enhance the supplied task with coverage information. This 186 * operation may fail if the task is being executed in the current VM 187 * 188 * @param task 189 * Task instance to enhance (usually an 190 * {@link UnknownElement}) 191 * @throws BuildException 192 * Thrown if this enhancer can handle this type of task, but 193 * this instance can not be enhanced for some reason. 194 */ enhanceTask(Task task)195 public void enhanceTask(Task task) throws BuildException; 196 } 197 } 198