Introduce new logging based runtime.
diff --git a/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java b/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
index 009b710..252276e 100644
--- a/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
+++ b/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
@@ -21,7 +21,7 @@
import org.jacoco.core.data.ExecutionDataWriter;
import org.jacoco.core.runtime.AgentOptions;
import org.jacoco.core.runtime.IRuntime;
-import org.jacoco.core.runtime.SystemPropertiesRuntime;
+import org.jacoco.core.runtime.LoggerRuntime;
/**
* The agent which is referred as the <code>Premain-Class</code>.
@@ -35,7 +35,7 @@
final Instrumentation inst) {
final AgentOptions options = new AgentOptions(agentArgs);
- final IRuntime runtime = new SystemPropertiesRuntime();
+ final IRuntime runtime = new LoggerRuntime();
runtime.startup();
inst.addTransformer(new CoverageTransformer(runtime, options));
diff --git a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
index 0ead0ad..341dda7 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
@@ -13,7 +13,7 @@
package org.jacoco.core.instr;
import org.jacoco.core.runtime.IRuntime;
-import org.jacoco.core.runtime.SystemPropertiesRuntime;
+import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.runtime.SystemPropertiesRuntimeTest.IWriteAccess;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +38,7 @@
@Before
public void setup() {
- runtime = new SystemPropertiesRuntime(0);
+ runtime = new LoggerRuntime();
instrumenter = new ClassInstrumenter(123, runtime, new EmptyVisitor());
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
new file mode 100644
index 0000000..7956950
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ * $Id: $
+ *******************************************************************************/
+package org.jacoco.core.runtime;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jacoco.core.data.IExecutionDataVisitor;
+import org.jacoco.core.test.TargetLoader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+
+/**
+ * Unit tests for {@link LoggerRuntime}.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public class LoggerRuntimeTest {
+
+ private IRuntime runtime;
+
+ private TestStorage storage;
+
+ @Before
+ public void setup() {
+ runtime = new LoggerRuntime();
+ runtime.startup();
+ storage = new TestStorage();
+ }
+
+ @After
+ public void shutdown() {
+ runtime.shutdown();
+ }
+
+ @Test
+ public void testCollectEmpty() {
+ runtime.collect(storage, false);
+ storage.assertSize(0);
+ }
+
+ @Test
+ public void testCollect1() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[3][];
+ generateAndInstantiateClass(1001, data1);
+ runtime.collect(storage, false);
+ storage.assertSize(1);
+ storage.assertData(1001, data1);
+ }
+
+ @Test
+ public void testCollect2() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[3][];
+ final boolean[][] data2 = new boolean[5][];
+ generateAndInstantiateClass(1001, data1);
+ generateAndInstantiateClass(1002, data2);
+ runtime.collect(storage, false);
+ storage.assertSize(2);
+ storage.assertData(1001, data1);
+ storage.assertData(1002, data2);
+ }
+
+ @Test
+ public void testReset() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[1][];
+ data1[0] = new boolean[] { true, true, true };
+ generateAndInstantiateClass(1001, data1);
+ runtime.reset();
+ assertFalse(data1[0][0]);
+ assertFalse(data1[0][1]);
+ assertFalse(data1[0][2]);
+ }
+
+ @Test
+ public void testCollectAndReset() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[1][];
+ data1[0] = new boolean[] { true, true, true };
+ generateAndInstantiateClass(1001, data1);
+ runtime.collect(storage, true);
+ storage.assertSize(1);
+ storage.assertData(1001, data1);
+ assertFalse(data1[0][0]);
+ assertFalse(data1[0][1]);
+ assertFalse(data1[0][2]);
+ }
+
+ /**
+ * Creates a new class with the given id, loads this class and injects the
+ * given data instance into the class. Used to check whether the data is
+ * properly collected in the runtime.
+ *
+ * @param classId
+ * @param data
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ */
+ private void generateAndInstantiateClass(int classId, boolean[][] data)
+ throws InstantiationException, IllegalAccessException {
+
+ final String className = "org/jacoco/test/targets/LoggerRuntimeTestTarget";
+ final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,
+ "java/lang/Object", new String[] { Type
+ .getInternalName(IWriteAccess.class) });
+
+ // Constructor
+ GeneratorAdapter gen = new GeneratorAdapter(writer.visitMethod(
+ Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[0]),
+ Opcodes.ACC_PUBLIC, "<init>", "()V");
+ gen.visitCode();
+ gen.loadThis();
+ gen.invokeConstructor(Type.getType(Object.class), new Method("<init>",
+ "()V"));
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+ writer.visitEnd();
+
+ // set()
+ gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC,
+ "set", "([[Z)V", null, new String[0]), Opcodes.ACC_PUBLIC,
+ "set", "([[Z)V");
+ gen.visitCode();
+ gen.loadArg(0);
+ runtime.generateRegistration(classId, gen);
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+ writer.visitEnd();
+
+ final TargetLoader loader = new TargetLoader(className
+ .replace('/', '.'), writer.toByteArray());
+ ((IWriteAccess) loader.newTargetInstance()).set(data);
+ }
+
+ /**
+ * With this interface we inject sample coverage data into the generated
+ * classes.
+ */
+ public interface IWriteAccess {
+
+ void set(boolean[][] data);
+
+ }
+
+ private static class TestStorage implements IExecutionDataVisitor {
+
+ private final Map<Long, boolean[][]> data = new HashMap<Long, boolean[][]>();
+
+ public void assertSize(int size) {
+ assertEquals(size, data.size(), 0.0);
+ }
+
+ public boolean[][] getData(long classId) {
+ return data.get(Long.valueOf(classId));
+ }
+
+ public void assertData(long classId, boolean[][] expected) {
+ assertSame(expected, getData(classId));
+ }
+
+ // === ICoverageDataVisitor ===
+
+ public void visitClassExecution(long id, boolean[][] blockdata) {
+ data.put(Long.valueOf(id), blockdata);
+ }
+
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
index aefd9f2..b13894a 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
@@ -17,7 +17,7 @@
import org.jacoco.core.instr.Analyzer;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.IRuntime;
-import org.jacoco.core.runtime.SystemPropertiesRuntime;
+import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.test.ClassDataRecorder.BlockData;
import org.jacoco.core.test.targets.Target_cinit_01;
import org.jacoco.core.test.targets.Target_cinit_02;
@@ -45,7 +45,7 @@
@Before
public void setup() {
- runtime = new SystemPropertiesRuntime();
+ runtime = new LoggerRuntime();
runtime.startup();
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java b/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
index 492c62c..515935f 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
@@ -19,7 +19,7 @@
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.IRuntime;
-import org.jacoco.core.runtime.SystemPropertiesRuntime;
+import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.test.targets.Target_performance_01;
import org.jacoco.core.test.targets.Target_performance_02;
import org.junit.Before;
@@ -41,7 +41,7 @@
@Before
public void setup() {
- runtime = new SystemPropertiesRuntime();
+ runtime = new LoggerRuntime();
runtime.startup();
}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
new file mode 100644
index 0000000..40e08b5
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ * $Id: $
+ *******************************************************************************/
+package org.jacoco.core.runtime;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import org.jacoco.core.data.IExecutionDataVisitor;
+import org.jacoco.core.instr.GeneratorConstants;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * This {@link IRuntime} implementation uses the Java logging API to report
+ * coverage data. The advantage is, that the instrumented classes do not get
+ * dependencies to other classes than the JRE library itself.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public class LoggerRuntime implements IRuntime {
+
+ private static final String CHANNEL = "jacoco-runtime";
+
+ private final String key;
+
+ private final Logger logger;
+
+ private final Handler handler;
+
+ final Map<Long, boolean[][]> dataMap;
+
+ /**
+ * Creates a new runtime.
+ */
+ public LoggerRuntime() {
+ this.key = Integer.toHexString(hashCode());
+ this.logger = configureLogger();
+ this.handler = new RuntimeHandler();
+ dataMap = Collections.synchronizedMap(new HashMap<Long, boolean[][]>());
+ }
+
+ private Logger configureLogger() {
+ final Logger l = Logger.getLogger(CHANNEL);
+ l.setUseParentHandlers(false);
+ l.setLevel(Level.ALL);
+ return l;
+ }
+
+ public void generateRegistration(final long classId,
+ final GeneratorAdapter gen) {
+
+ // boolean[][] data = pop()
+ final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);
+ gen.storeLocal(data);
+
+ // stack := Logger.getLogger(CHANNEL)
+ gen.push(CHANNEL);
+ gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
+ "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
+
+ // stack = Level.INFO;
+ gen.getStatic(Type.getObjectType("java/util/logging/Level"), "INFO",
+ Type.getObjectType("java/util/logging/Level"));
+
+ // stack := key
+ gen.push(key);
+
+ // stack := new Object[2]
+ gen.push(2);
+ gen.newArray(Type.getObjectType("java/lang/Object"));
+
+ // stack[0] = Long.valueOf(classId)
+ gen.dup();
+ gen.push(0);
+ gen.push(classId);
+ gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
+ "(J)Ljava/lang/Long;");
+ gen.arrayStore(Type.getObjectType("java/lang/Object"));
+
+ // stack[1] = data
+ gen.dup();
+ gen.push(1);
+ gen.loadLocal(data);
+ gen.arrayStore(Type.getObjectType("java/lang/Object"));
+
+ // stack.log(stack, stack, stack)
+ gen
+ .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "java/util/logging/Logger", "log",
+ "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
+ }
+
+ public void startup() {
+ this.logger.addHandler(handler);
+ }
+
+ public void collect(final IExecutionDataVisitor visitor, final boolean reset) {
+ synchronized (dataMap) {
+ for (final Map.Entry<Long, boolean[][]> entry : dataMap.entrySet()) {
+ final long classId = entry.getKey().longValue();
+ final boolean[][] blockData = entry.getValue();
+ visitor.visitClassExecution(classId, blockData);
+ }
+ if (reset) {
+ reset();
+ }
+ }
+ }
+
+ public void reset() {
+ synchronized (dataMap) {
+ for (final boolean[][] data : dataMap.values()) {
+ for (final boolean[] arr : data) {
+ Arrays.fill(arr, false);
+ }
+ }
+ }
+ }
+
+ public void shutdown() {
+ this.logger.removeHandler(handler);
+ }
+
+ private class RuntimeHandler extends Handler {
+
+ @Override
+ public void publish(final LogRecord record) {
+ if (key.equals(record.getMessage())) {
+ final Object[] params = record.getParameters();
+ dataMap.put((Long) params[0], (boolean[][]) params[1]);
+ }
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ }
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
index 8917b84..9f93f60 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -43,19 +43,7 @@
private final String key;
/**
- * Creates a new runtime with the given id. The id helps to separate
- * different runtime instances. The instrumentation and the target VM must
- * be based on a runtime with the same id.
- *
- * @param id
- * Identifier for the runtime
- */
- public SystemPropertiesRuntime(final int id) {
- this.key = KEYPREFIX + Integer.toHexString(id);
- }
-
- /**
- * Creates a new runtime with a random identifier.
+ * Creates a new runtime.
*/
public SystemPropertiesRuntime() {
this.key = KEYPREFIX + hashCode();
@@ -69,13 +57,11 @@
final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);
gen.storeLocal(data);
- // Properties stack := System.getProperties()
+ // stack := System.getProperties()
gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"getProperties", "()Ljava/util/Properties;");
- // gen.swap();
-
- // Map stack := stack.get(key)
+ // stack := stack.get(key)
gen.push(key);
gen.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/Properties",
"get", "(Ljava/lang/Object;)Ljava/lang/Object;");
@@ -85,7 +71,7 @@
gen.push(classId);
gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
"(J)Ljava/lang/Long;");
- // gen.swap();
+
gen.loadLocal(data);
gen.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index 56ddf1c..a835f91 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -222,9 +222,21 @@
Making a runtime library available to all instrumented classes can be a
painful or impossible task in frameworks that use there own class loading
mechanisms. Therefore JaCoCo decouples the instrumented classes and the
- coverage runtime through official JRE API types.
+ coverage runtime through official JRE API types. Currently two approaches has
+ been implemented:
</p>
+<ul>
+ <li>Use a shared <code>java.util.logging.Logger</code> instance to report
+ coverage data to. The coverage runtime registers a custom
+ <code>Handler</code> to receive the data. (Default)</li>
+ <li>Another approach was to store a <code>java.util.Map</code> instance
+ under a system property. This solution breaks the contract that system
+ properties must only contain <code>java.lang.String</code> values and has
+ therefore caused trouble in certain environments.</li>
+</ul>
+
+
<h2>Memory Usage</h2>
<p class="intro">