#23: Share execution data porperly if the same class definition is loaded multiple times.
diff --git a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
index bdf3a4e..523d55f 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
@@ -65,7 +65,7 @@
boolean[][] blocks = createBlockdata(0, 0);
writer.visitClassExecution(Long.MIN_VALUE, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(Long.MIN_VALUE));
+ assertArrayEquals(blocks, store.get(Long.MIN_VALUE));
}
@Test
@@ -73,7 +73,7 @@
boolean[][] blocks = createBlockdata(0, 0);
writer.visitClassExecution(Long.MAX_VALUE, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(Long.MAX_VALUE));
+ assertArrayEquals(blocks, store.get(Long.MAX_VALUE));
}
@Test
@@ -81,7 +81,7 @@
boolean[][] blocks = createBlockdata(0, 0);
writer.visitClassExecution(3, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(3));
+ assertArrayEquals(blocks, store.get(3));
}
@Test
@@ -89,7 +89,7 @@
boolean[][] blocks = createBlockdata(5, 0);
writer.visitClassExecution(3, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(3));
+ assertArrayEquals(blocks, store.get(3));
}
@Test
@@ -97,7 +97,7 @@
boolean[][] blocks = createBlockdata(5, 10);
writer.visitClassExecution(3, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(3));
+ assertArrayEquals(blocks, store.get(3));
}
@Test
@@ -107,8 +107,8 @@
writer.visitClassExecution(333, blocks1);
writer.visitClassExecution(-45, blocks2);
readIntoStore();
- assertArrayEquals(blocks1, store.getBlockdata(333));
- assertArrayEquals(blocks2, store.getBlockdata(-45));
+ assertArrayEquals(blocks1, store.get(333));
+ assertArrayEquals(blocks2, store.get(-45));
}
@Test
@@ -116,7 +116,7 @@
boolean[][] blocks = createBlockdata(43, 40);
writer.visitClassExecution(123, blocks);
readIntoStore();
- assertArrayEquals(blocks, store.getBlockdata(123));
+ assertArrayEquals(blocks, store.get(123));
}
private boolean[][] createBlockdata(int methodCount, int maxBlockCount) {
diff --git a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
index 71b8fbb..dc259e6 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
@@ -14,6 +14,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -44,7 +45,7 @@
@Test
public void testEmpty() {
- assertNull(store.getBlockdata(123));
+ assertNull(store.get(123));
store.accept(this);
assertEquals(Collections.emptyMap(), output);
}
@@ -53,8 +54,8 @@
public void testPut() {
boolean[][] data = new boolean[][] { new boolean[] { false },
new boolean[] { false, true } };
- store.visitClassExecution(1000, data);
- assertSame(data, store.getBlockdata(1000));
+ store.put(1000, data);
+ assertSame(data, store.get(1000));
store.accept(this);
assertEquals(Collections.singletonMap(Long.valueOf(1000), data), output);
}
@@ -68,7 +69,7 @@
new boolean[] { true, false } };
store.visitClassExecution(1000, data2);
- final boolean[][] result = store.getBlockdata(1000);
+ final boolean[][] result = store.get(1000);
assertFalse(result[0][0]);
assertTrue(result[0][1]);
assertTrue(result[1][0]);
@@ -94,6 +95,20 @@
store.visitClassExecution(1000, data2);
}
+ @Test
+ public void testReset() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[1][];
+ data1[0] = new boolean[] { true, true, true };
+ store.put(1000, data1);
+ store.reset();
+ boolean[][] data2 = store.get(1000);
+ assertNotNull(data2);
+ assertFalse(data2[0][0]);
+ assertFalse(data2[0][1]);
+ assertFalse(data2[0][2]);
+ }
+
// === IExecutionDataOutput ===
public void visitClassExecution(long id, boolean[][] blockdata) {
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 341dda7..deea5f1 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
@@ -14,7 +14,6 @@
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.LoggerRuntime;
-import org.jacoco.core.runtime.SystemPropertiesRuntimeTest.IWriteAccess;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassVisitor;
@@ -51,8 +50,7 @@
final String className = "org/jacoco/test/targets/ClassInstrumenterTestTarget";
visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,
- "java/lang/Object", new String[] { Type
- .getInternalName(IWriteAccess.class) });
+ "java/lang/Object", new String[] {});
// Constructor
GeneratorAdapter gen = new GeneratorAdapter(visitor.visitMethod(
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
index 7956950..8ee9491 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
@@ -12,23 +12,6 @@
*******************************************************************************/
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}.
@@ -36,160 +19,11 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class LoggerRuntimeTest {
+public class LoggerRuntimeTest extends RuntimeTestBase {
- 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);
- }
-
+ @Override
+ IRuntime createRuntime() {
+ return new LoggerRuntime();
}
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
new file mode 100644
index 0000000..a635731
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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 static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jacoco.core.data.IExecutionDataVisitor;
+import org.jacoco.core.instr.GeneratorConstants;
+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;
+
+/**
+ * Abstract test base for {@link IRuntime} implementations.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public abstract class RuntimeTestBase {
+
+ private IRuntime runtime;
+
+ private TestStorage storage;
+
+ abstract IRuntime createRuntime();
+
+ @Before
+ public void setup() {
+ runtime = createRuntime();
+ 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 testReset() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data1 = new boolean[1][];
+ data1[0] = new boolean[] { true, true, true };
+ runtime.registerClass(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 };
+ runtime.registerClass(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]);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNoClassRegistration() throws InstantiationException,
+ IllegalAccessException {
+ generateAndInstantiateClass(1001);
+ }
+
+ @Test
+ public void testDataAccessor() throws InstantiationException,
+ IllegalAccessException {
+ final boolean[][] data = newStructure();
+ runtime.registerClass(1001, data);
+ ITarget t = generateAndInstantiateClass(1001);
+ assertSame(data, t.get());
+ }
+
+ @Test
+ public void testExecutionRecording() throws InstantiationException,
+ IllegalAccessException {
+ boolean[][] data1 = newStructure();
+ runtime.registerClass(1001, data1);
+ generateAndInstantiateClass(1001).a();
+ runtime.collect(storage, false);
+ storage.assertSize(1);
+ storage.assertData(1001, data1);
+ assertTrue(data1[0][0]);
+ assertFalse(data1[1][0]);
+ }
+
+ @Test
+ public void testLoadSameClassTwice() throws InstantiationException,
+ IllegalAccessException {
+ boolean[][] data1 = newStructure();
+ runtime.registerClass(1001, data1);
+ generateAndInstantiateClass(1001).a();
+ generateAndInstantiateClass(1001).b();
+ runtime.collect(storage, false);
+ storage.assertSize(1);
+ storage.assertData(1001, data1);
+ assertTrue(data1[0][0]);
+ assertTrue(data1[1][0]);
+ }
+
+ /**
+ * 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 ITarget generateAndInstantiateClass(int classid)
+ throws InstantiationException, IllegalAccessException {
+
+ final String className = "org/jacoco/test/targets/RuntimeTestTarget_"
+ + classid;
+ Type classType = Type.getObjectType(className);
+
+ 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(ITarget.class) });
+
+ writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "data",
+ "[[Z", null, null);
+
+ // 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.loadThis();
+ runtime.generateDataAccessor(classid, gen);
+ gen.putField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+
+ // get()
+ gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC,
+ "get", "()[[Z", null, new String[0]), Opcodes.ACC_PUBLIC,
+ "get", "()[[Z");
+ gen.visitCode();
+ gen.loadThis();
+ gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+
+ // a()
+ gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, "a",
+ "()V", null, new String[0]), Opcodes.ACC_PUBLIC, "a", "()V");
+ gen.visitCode();
+ gen.loadThis();
+ gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);
+ gen.push(0);
+ gen.arrayLoad(Type.getObjectType("[Z"));
+ gen.push(0);
+ gen.push(1);
+ gen.arrayStore(Type.BOOLEAN_TYPE);
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+
+ // a()
+ gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, "b",
+ "()V", null, new String[0]), Opcodes.ACC_PUBLIC, "b", "()V");
+ gen.visitCode();
+ gen.loadThis();
+ gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);
+ gen.push(1);
+ gen.arrayLoad(Type.getObjectType("[Z"));
+ gen.push(0);
+ gen.push(1);
+ gen.arrayStore(Type.BOOLEAN_TYPE);
+ gen.returnValue();
+ gen.visitMaxs(0, 0);
+ gen.visitEnd();
+
+ writer.visitEnd();
+
+ final TargetLoader loader = new TargetLoader(className
+ .replace('/', '.'), writer.toByteArray());
+ return (ITarget) loader.newTargetInstance();
+ }
+
+ /**
+ * With this interface we inject sample coverage data into the generated
+ * classes.
+ */
+ public interface ITarget {
+
+ boolean[][] get();
+
+ // implementations just mark method 0 as executed
+ void a();
+
+ // implementations just mark method 1 as executed
+ void b();
+
+ }
+
+ private boolean[][] newStructure() {
+ return new boolean[][] { new boolean[] { false },
+ new boolean[] { false } };
+ }
+
+ 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/runtime/SystemPropertiesRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
index c0c8f08..6c955f5 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
@@ -12,190 +12,17 @@
*******************************************************************************/
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 SystemPropertiesRuntime}.
*
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class SystemPropertiesRuntimeTest {
+public class SystemPropertiesRuntimeTest extends RuntimeTestBase {
- private IRuntime runtime;
-
- private TestStorage storage;
-
- @Before
- public void setup() {
- runtime = new SystemPropertiesRuntime();
- 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]);
- }
-
- @Test(expected = IllegalStateException.class)
- public void testShutdown() {
- runtime.shutdown();
- runtime.collect(storage, false);
- }
-
- /**
- * 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/SystemPropertiesRuntimeTestTarget";
- 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);
- }
-
+ @Override
+ IRuntime createRuntime() {
+ return new SystemPropertiesRuntime();
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
index e3c33f4..0436854 100644
--- a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
+++ b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
@@ -87,7 +87,7 @@
public IClassStructureVisitor visitClassStructure(final long id,
final String name) {
- final boolean[][] covered = executionData.getBlockdata(id);
+ final boolean[][] covered = executionData.get(id);
final Collection<MethodCoverage> methods = new ArrayList<MethodCoverage>();
final String[] sourcename = new String[1];
return new IClassStructureVisitor() {
diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
index 24515f6..a4aa72f 100644
--- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
+++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
@@ -12,6 +12,7 @@
*******************************************************************************/
package org.jacoco.core.data;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -29,15 +30,39 @@
private final Map<Long, boolean[][]> data = new HashMap<Long, boolean[][]>();
- public void visitClassExecution(final long classid, boolean[][] blockdata) {
- final Long id = Long.valueOf(classid);
- final boolean[][] current = data.get(id);
+ /**
+ * Adds the given block data structure into the store. If there is already a
+ * data structure for this class ID, this structure is merged with the given
+ * one. In this case a {@link IllegalStateException} is thrown, if both
+ * executions data structure do have different bloc sizes.
+ *
+ * @param classid
+ * unique class identifier
+ * @param blockdata
+ * execution data
+ */
+ public void put(final Long classid, boolean[][] blockdata) {
+ final boolean[][] current = data.get(classid);
if (current != null) {
merge(current, blockdata);
blockdata = current;
}
- data.put(id, blockdata);
+ data.put(classid, blockdata);
+ }
+ /**
+ * Adds the given block data structure into the store. If there is already a
+ * data structure for this class ID, this structure is merged with the given
+ * one. In this case a {@link IllegalStateException} is thrown, if both
+ * executions data structure do have different bloc sizes.
+ *
+ * @param classid
+ * unique class identifier
+ * @param blockdata
+ * execution data
+ */
+ public void put(final long classid, final boolean[][] blockdata) {
+ put(Long.valueOf(classid), blockdata);
}
private static void merge(final boolean[][] target, final boolean[][] data) {
@@ -68,8 +93,32 @@
* class identifier
* @return coverage data or <code>null</code>
*/
- public boolean[][] getBlockdata(final long classid) {
- return data.get(Long.valueOf(classid));
+ public boolean[][] get(final long classid) {
+ return get(Long.valueOf(classid));
+ }
+
+ /**
+ * Returns the coverage data for the class with the given identifier if
+ * available.
+ *
+ * @param classid
+ * class identifier
+ * @return coverage data or <code>null</code>
+ */
+ public boolean[][] get(final Long classid) {
+ return data.get(classid);
+ }
+
+ /**
+ * Resets all execution data structures, i.e. marks them as not executed.
+ * The data structures itself are not deleted.
+ */
+ public void reset() {
+ for (final boolean[][] struct : data.values()) {
+ for (final boolean[] arr : struct) {
+ Arrays.fill(arr, false);
+ }
+ }
}
/**
@@ -85,4 +134,11 @@
}
}
+ // === IExecutionDataVisitor ===
+
+ public void visitClassExecution(final long classid,
+ final boolean[][] blockdata) {
+ put(classid, blockdata);
+ }
+
}
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
index 187bd7d..4db6958 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
@@ -121,6 +121,7 @@
public void visitEnd() {
createDataField();
createInitMethod();
+ registerClass();
super.visitEnd();
}
@@ -173,42 +174,12 @@
* generator to emit code to
*/
private void genInitializeDataField(final GeneratorAdapter gen) {
- genInstantiateDataArray(gen); // ................ Stack: [[Z
+ runtime.generateDataAccessor(id, gen);// ........ Stack: [[Z
gen.dup(); // ................................... Stack: [[Z [[Z
gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,
GeneratorConstants.DATAFIELD_TYPE);
// .............................................. Stack: [[Z
-
- gen.dup(); // ................................... Stack: [[Z [[Z
- runtime.generateRegistration(id, gen);
-
- // .............................................. Stack: [[Z
- }
-
- /**
- * Generates the byte code to instantiate the 2-dimensional block data
- * array. Each boolean[] entry is created in a length equals to the number
- * of blocks in the corresponding method.
- *
- * The code will push the [[Z data array on the operand stack.
- *
- * TODO: Let the coverage runtime generate this structure
- *
- * @param gen
- * generator to emit code to
- */
- private void genInstantiateDataArray(final GeneratorAdapter gen) {
- gen.push(blockCounters.size()); // .............. Stack: I
- gen.newArray(GeneratorConstants.BLOCK_ARR); // .. Stack: [[Z
- for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {
- gen.dup(); // ............................... Stack: [[Z, [[Z
- gen.push(blockIdx); // ...................... Stack: [[Z, [[Z, I
- gen.push(blockCounters.get(blockIdx).getBlockCount());
- // .......................................... Stack: [[Z, [[Z, I, I
- gen.newArray(Type.BOOLEAN_TYPE);// .......... Stack: [[Z, [[Z, I, [Z
- gen.arrayStore(GeneratorConstants.BLOCK_ARR); // Stack: [[Z
- }
}
/**
@@ -232,4 +203,17 @@
}
}
+ /**
+ * Create a execution data structure according to the structure of this
+ * class and registers it with the runtime.
+ */
+ private void registerClass() {
+ final boolean[][] data = new boolean[blockCounters.size()][];
+ for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {
+ data[blockIdx] = new boolean[blockCounters.get(blockIdx)
+ .getBlockCount()];
+ }
+ runtime.registerClass(id, data);
+ }
+
}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
new file mode 100644
index 0000000..708b038
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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 org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.data.IExecutionDataVisitor;
+
+/**
+ * Base {@link IRuntime} implementation.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public abstract class AbstractRuntime implements IRuntime {
+
+ /** store for execution data */
+ protected final ExecutionDataStore store;
+
+ /**
+ * Creates a new runtime.
+ */
+ protected AbstractRuntime() {
+ store = new ExecutionDataStore();
+ }
+
+ public final void collect(final IExecutionDataVisitor visitor,
+ final boolean reset) {
+ synchronized (store) {
+ store.accept(visitor);
+ if (reset) {
+ store.reset();
+ }
+ }
+ }
+
+ public final void registerClass(final long classid,
+ final boolean[][] blockdata) {
+ store.put(classid, blockdata);
+ }
+
+ public final void reset() {
+ synchronized (store) {
+ store.reset();
+ }
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
index 0be60d4..167e7a7 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
@@ -25,23 +25,22 @@
public interface IRuntime {
/**
- * This method generates the byte code required to register the coverage
- * data structure of the class with the given id. Typically the
- * instrumentation process will embed this code into a method that is called
- * on class initialization. This method can be called at any time even
- * outside the target VM.
+ * This method generates the byte code required to obtain the coverage data
+ * structure for the class with the given id. Typically the instrumentation
+ * process will embed this code into a method that is called on class
+ * initialization. This method can be called at any time even outside the
+ * target VM.
*
- * The generated code must pop a <code>byte[][]</code> instance from the
- * operand stack. Except this object on the stack the generated code must
- * not make any assumptions about the structure of the embedding method or
- * class.
+ * The generated code must push a <code>byte[][]</code> instance to the
+ * operand stack. Except this result object the generated code must not make
+ * any assumptions about the structure of the embedding method or class.
*
- * @param classId
+ * @param classid
* identifier of the class
* @param gen
* code output
*/
- public void generateRegistration(long classId, GeneratorAdapter gen);
+ public void generateDataAccessor(long classid, GeneratorAdapter gen);
/**
* Starts the coverage runtime. This method MUST be called before any class
@@ -56,6 +55,18 @@
public void shutdown();
/**
+ * Before a particular class gets loaded, its execution data structure must
+ * be registered with the runtime through this method. This method must only
+ * be called between {@link #startup()} and {@link #shutdown()}.
+ *
+ * @param classid
+ * identifier of the class
+ * @param blockdata
+ * execution data structure for this method
+ */
+ public void registerClass(long classid, boolean[][] blockdata);
+
+ /**
* Collects the current execution data and writes it to the given
* {@link IExecutionDataVisitor} object. This method must only be called
* between {@link #startup()} and {@link #shutdown()}.
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
index 40e08b5..924fcb2 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -12,16 +12,11 @@
*******************************************************************************/
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;
@@ -31,11 +26,18 @@
* 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.
+ * <p>
+ *
+ * The implementation uses a dedicated log channel. Instrumented classes call
+ * {@link Logger#log(Level, String, Object[])} with the class identifier in the
+ * first slot of the parameter array. The runtime implements a {@link Handler}
+ * for this channel that puts the block data structure into the first slot of
+ * the parameter array.
*
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class LoggerRuntime implements IRuntime {
+public class LoggerRuntime extends AbstractRuntime {
private static final String CHANNEL = "jacoco-runtime";
@@ -45,8 +47,6 @@
private final Handler handler;
- final Map<Long, boolean[][]> dataMap;
-
/**
* Creates a new runtime.
*/
@@ -54,7 +54,6 @@
this.key = Integer.toHexString(hashCode());
this.logger = configureLogger();
this.handler = new RuntimeHandler();
- dataMap = Collections.synchronizedMap(new HashMap<Long, boolean[][]>());
}
private Logger configureLogger() {
@@ -64,12 +63,28 @@
return l;
}
- public void generateRegistration(final long classId,
+ public void generateDataAccessor(final long classid,
final GeneratorAdapter gen) {
- // boolean[][] data = pop()
- final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);
- gen.storeLocal(data);
+ // 1. Create parameter array:
+
+ final int param = gen.newLocal(Type.getObjectType("java/lang/Object"));
+
+ // stack := new Object[1]
+ gen.push(1);
+ 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"));
+
+ gen.storeLocal(param);
+
+ // 2. Call Logger:
// stack := Logger.getLogger(CHANNEL)
gen.push(CHANNEL);
@@ -83,58 +98,27 @@
// 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 := param
+ gen.loadLocal(param);
// 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");
+
+ // 3. Load data structure from parameter array:
+
+ gen.loadLocal(param);
+ gen.push(0);
+ gen.arrayLoad(GeneratorConstants.DATAFIELD_TYPE);
+ gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);
}
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);
}
@@ -145,7 +129,15 @@
public void publish(final LogRecord record) {
if (key.equals(record.getMessage())) {
final Object[] params = record.getParameters();
- dataMap.put((Long) params[0], (boolean[][]) params[1]);
+ final Long id = (Long) params[0];
+ synchronized (store) {
+ final boolean[][] blockdata = store.get(id);
+ if (blockdata == null) {
+ throw new IllegalStateException("Unknown class ID: "
+ + id);
+ }
+ params[0] = blockdata;
+ }
}
}
@@ -155,6 +147,14 @@
@Override
public void close() throws SecurityException {
+ // The Java logging framework removes and closes all handlers on JVM
+ // shutdown. As soon as our handler has been removed, all classes
+ // that might get instrumented during shutdown (e.g. loaded by other
+ // shutdown hooks) will fail to initialize. Therefore we add ourself
+ // again here.
+ // This is a nasty hack that might fail in some Java
+ // implementations.
+ logger.addHandler(handler);
}
}
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 9f93f60..d333fe2 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -12,18 +12,17 @@
*******************************************************************************/
package org.jacoco.core.runtime;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.Collection;
import java.util.Map;
+import java.util.Set;
-import org.jacoco.core.data.IExecutionDataVisitor;
import org.jacoco.core.instr.GeneratorConstants;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
/**
- * This {@link IRuntime} implementation places the coverage data in the
+ * This {@link IRuntime} implementation makes the execution data available
+ * through a special entry of the type {@link Map} in the
* {@link System#getProperties()} hash table. The advantage is, that the
* instrumented classes do not get dependencies to other classes than the JRE
* library itself.
@@ -36,12 +35,70 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class SystemPropertiesRuntime implements IRuntime {
+public class SystemPropertiesRuntime extends AbstractRuntime {
private static final String KEYPREFIX = "jacoco-";
private final String key;
+ private final Map<Long, boolean[][]> dataAccess = new Map<Long, boolean[][]>() {
+
+ public boolean[][] get(final Object key) {
+ final Long id = (Long) key;
+ synchronized (store) {
+ final boolean[][] blockdata = store.get(id);
+ if (blockdata == null) {
+ throw new IllegalStateException("Unknown class ID: " + id);
+ }
+ return blockdata;
+ }
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsKey(final Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsValue(final Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<Entry<Long, boolean[][]>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isEmpty() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<Long> keySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean[][] put(final Long key, final boolean[][] value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(final Map<? extends Long, ? extends boolean[][]> t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean[][] remove(final Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<boolean[][]> values() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
/**
* Creates a new runtime.
*/
@@ -49,14 +106,9 @@
this.key = KEYPREFIX + hashCode();
}
- // TODO: lokale Variable vermeiden (swap!)
- public void generateRegistration(final long classId,
+ public void generateDataAccessor(final long classid,
final GeneratorAdapter gen) {
- // boolean[][] data = pop()
- final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);
- gen.storeLocal(data);
-
// stack := System.getProperties()
gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"getProperties", "()Ljava/util/Properties;");
@@ -67,55 +119,18 @@
"get", "(Ljava/lang/Object;)Ljava/lang/Object;");
gen.visitTypeInsn(Opcodes.CHECKCAST, "java/util/Map");
- // stack.put(classId, data)
- gen.push(classId);
+ // stack := stack.get(classid)
+ gen.push(classid);
gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
"(J)Ljava/lang/Long;");
- gen.loadLocal(data);
- gen.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",
- "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
- gen.pop();
+ gen.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",
+ "(Ljava/lang/Object;)Ljava/lang/Object;");
+ gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);
}
public void startup() {
- final Map<Long, boolean[][]> dataMap = Collections
- .synchronizedMap(new HashMap<Long, boolean[][]>());
- System.getProperties().put(key, dataMap);
- }
-
- @SuppressWarnings("unchecked")
- private Map<Long, boolean[][]> getDataMap() {
- final Object object = System.getProperties().get(key);
- if (object == null) {
- throw new IllegalStateException("Runtime not started.");
- }
- return (Map<Long, boolean[][]>) object;
- }
-
- public void collect(final IExecutionDataVisitor visitor, final boolean reset) {
- final Map<Long, boolean[][]> dataMap = getDataMap();
- 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() {
- final Map<Long, boolean[][]> dataMap = getDataMap();
- synchronized (dataMap) {
- for (final boolean[][] data : dataMap.values()) {
- for (final boolean[] arr : data) {
- Arrays.fill(arr, false);
- }
- }
- }
+ System.getProperties().put(key, dataAccess);
}
public void shutdown() {
diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index a835f91..39d3865 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -222,14 +222,16 @@
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. Currently two approaches has
+ coverage runtime through official JRE API types. Currently two approaches have
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>By default we 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. The problem with this approach is
+ that the logging framework removes all handlers during shutdown. This may
+ break classes that get initialized during JVM shutdown.</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