blob: 8a9688404395dd8b38c363875b10e4390bf996d4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
* 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
*
*******************************************************************************/
package org.jacoco.core.runtime;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import org.jacoco.core.test.TargetLoader;
import org.junit.Test;
/**
* Unit tests for {@link ModifiedSystemClassRuntime}.
*/
public class ModifiedSystemClassRuntimeTest extends RuntimeTestBase {
@Override
IRuntime createRuntime() {
return new ModifiedSystemClassRuntime(
ModifiedSystemClassRuntimeTest.class, "accessField");
}
@Test(expected = RuntimeException.class)
public void testCreateForNegative() throws Exception {
Instrumentation inst = newInstrumentationMock();
ModifiedSystemClassRuntime.createFor(inst, TARGET_CLASS_NAME);
}
/** This static member emulate the instrumented system class. */
public static Object accessField;
private static final String TARGET_CLASS_NAME = "org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest";
/**
* Note that we use Proxy here to mock {@link Instrumentation}, because JDK
* 9 adds new method "addModule", whose parameter depends on class
* "java.lang.reflect.Module" introduced in JDK 9.
*/
private Instrumentation newInstrumentationMock() {
return (Instrumentation) Proxy.newProxyInstance(getClass()
.getClassLoader(), new Class[] { Instrumentation.class },
new MyInvocationHandler());
}
private static class MyInvocationHandler implements InvocationHandler {
boolean added = false;
boolean removed = false;
/**
* {@link Instrumentation#addTransformer(ClassFileTransformer)}
*/
void addTransformer(ClassFileTransformer transformer) {
assertFalse(added);
added = true;
try {
// Our class should get instrumented:
final byte[] data = TargetLoader
.getClassDataAsBytes(ModifiedSystemClassRuntimeTest.class);
verifyInstrumentedClass(TARGET_CLASS_NAME,
transformer.transform((ClassLoader) null,
TARGET_CLASS_NAME, null, null, data));
// Other classes will not be instrumented:
assertNull(transformer.transform(getClass().getClassLoader(),
"some/other/Class", null, null, new byte[0]));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* {@link Instrumentation#removeTransformer(ClassFileTransformer)}
*/
Boolean removeTransformer() {
assertTrue(added);
assertFalse(removed);
removed = true;
return Boolean.TRUE;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (args.length == 1) {
if ("removeTransformer".equals(method.getName())) {
return removeTransformer();
} else if ("addTransformer".equals(method.getName())) {
addTransformer((ClassFileTransformer) args[0]);
return null;
}
}
fail();
return null;
}
}
private static void verifyInstrumentedClass(String name, byte[] source)
throws Exception {
name = name.replace('/', '.');
final Class<?> targetClass = new TargetLoader().add(name, source);
// Check added field:
final Field f = targetClass.getField("$jacocoAccess");
assertTrue(Modifier.isPublic(f.getModifiers()));
assertTrue(Modifier.isStatic(f.getModifiers()));
assertTrue(Modifier.isTransient(f.getModifiers()));
assertEquals(Object.class, f.getType());
}
}