aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.ant/src/org/jacoco/ant/CoverageTask.java
blob: 6115b11d026af2af2bd26b27d29823390fc261a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/*******************************************************************************
 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Evgeny Mandrikov - TestNG support
 *    Brock Janiczak - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.ant;

import static java.lang.String.format;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
import org.apache.tools.ant.UnknownElement;

/**
 * Container task to run Java/JUnit tasks with the JaCoCo agent jar. Coverage
 * will only be applied if all of the following are true:
 * <ul>
 * <li>Exactly one sub task may be present</li>
 * <li>Task must be either Java or JUnit</li>
 * <li>Task must be using a forked VM (so vm args can be passed)</li>
 * </ul>
 */
public class CoverageTask extends AbstractCoverageTask
		implements TaskContainer {

	private final Collection<TaskEnhancer> taskEnhancers = new ArrayList<TaskEnhancer>();
	private Task childTask;

	/**
	 * Creates a new default coverage task
	 */
	public CoverageTask() {
		super();
		taskEnhancers.add(new JavaLikeTaskEnhancer("java"));
		taskEnhancers.add(new JavaLikeTaskEnhancer("junit"));
		taskEnhancers.add(new TestNGTaskEnhancer("testng"));
	}

	/**
	 * Add child task to this container and reconfigure it to run with coverage
	 * enabled
	 */
	public void addTask(final Task task) {
		if (childTask != null) {
			throw new BuildException(
					"Only one child task can be supplied to the coverge task",
					getLocation());
		}

		this.childTask = task;

		final String subTaskTypeName = task.getTaskType();

		final TaskEnhancer enhancer = findEnhancerForTask(subTaskTypeName);
		if (enhancer == null) {
			throw new BuildException(
					format("%s is not a valid child of the coverage task",
							subTaskTypeName),
					getLocation());
		}

		if (isEnabled()) {
			log(format("Enhancing %s with coverage", childTask.getTaskName()));
			enhancer.enhanceTask(task);
		}

		task.maybeConfigure();
	}

	private TaskEnhancer findEnhancerForTask(final String taskName) {
		for (final TaskEnhancer enhancer : taskEnhancers) {
			if (enhancer.supportsTask(taskName)) {
				return enhancer;
			}
		}

		return null;
	}

	/**
	 * Executes subtask and performs any required cleanup
	 */
	@Override
	public void execute() throws BuildException {
		if (childTask == null) {
			throw new BuildException(
					"A child task must be supplied for the coverage task",
					getLocation());
		}

		childTask.execute();
	}

	/**
	 * Task enhancer for TestNG. TestNG task always run in a forked VM and has
	 * nested jvmargs elements
	 */
	private class TestNGTaskEnhancer extends JavaLikeTaskEnhancer {

		public TestNGTaskEnhancer(final String supportedTaskName) {
			super(supportedTaskName);
		}

		@Override
		public void enhanceTask(final Task task) {
			addJvmArgs(task);
		}

	}

	/**
	 * Basic task enhancer that can handle all 'java like' tasks. That is, tasks
	 * that have a top level fork attribute and nested jvmargs elements
	 */
	private class JavaLikeTaskEnhancer implements TaskEnhancer {

		private final String supportedTaskName;

		public JavaLikeTaskEnhancer(final String supportedTaskName) {
			this.supportedTaskName = supportedTaskName;
		}

		public boolean supportsTask(final String taskname) {
			return taskname.equals(supportedTaskName);
		}

		public void enhanceTask(final Task task) {
			final RuntimeConfigurable configurableWrapper = task
					.getRuntimeConfigurableWrapper();

			final String forkValue = getProject().replaceProperties(
					(String) configurableWrapper.getAttributeMap().get("fork"));

			if (!Project.toBoolean(forkValue)) {
				throw new BuildException(
						"Coverage can only be applied on a forked VM",
						getLocation());
			}

			addJvmArgs(task);
		}

		public void addJvmArgs(final Task task) {
			final UnknownElement el = new UnknownElement("jvmarg");
			el.setTaskName("jvmarg");
			el.setQName("jvmarg");

			final RuntimeConfigurable runtimeConfigurableWrapper = el
					.getRuntimeConfigurableWrapper();
			runtimeConfigurableWrapper.setAttribute("value",
					getLaunchingArgument());

			task.getRuntimeConfigurableWrapper()
					.addChild(runtimeConfigurableWrapper);

			((UnknownElement) task).addChild(el);
		}
	}

	/**
	 * The task enhancer is responsible for potentially reconfiguring a task to
	 * support running with code coverage enabled
	 */
	private interface TaskEnhancer {
		/**
		 * @param taskname
		 *            Task type to enhance
		 * @return <code>true</code> if this enhancer is capable of enhancing
		 *         the requested task type
		 */
		boolean supportsTask(String taskname);

		/**
		 * Attempt to enhance the supplied task with coverage information. This
		 * operation may fail if the task is being executed in the current VM
		 *
		 * @param task
		 *            Task instance to enhance (usually an
		 *            {@link UnknownElement})
		 * @throws BuildException
		 *             Thrown if this enhancer can handle this type of task, but
		 *             this instance can not be enhanced for some reason.
		 */
		void enhanceTask(Task task) throws BuildException;
	}
}