/******************************************************************************* * Copyright (c) 2009, 2017 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()); } }