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