/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.test.rule; import com.liferay.portal.kernel.process.ClassPathUtil; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * @author Shuyang Zhou */ public class CodeCoverageAssertor implements TestRule { public static final CodeCoverageAssertor INSTANCE = new CodeCoverageAssertor(); public CodeCoverageAssertor() { this(null, null, true); } public CodeCoverageAssertor( String[] includes, String[] excludes, boolean includeInnerClasses) { _includes = includes; _excludes = excludes; _includeInnerClasses = includeInnerClasses; } public void appendAssertClasses(List<Class<?>> assertClasses) { } @Override public Statement apply( final Statement statement, final Description description) { if (description.getMethodName() != null) { return statement; } return new Statement() { @Override public void evaluate() throws Throwable { String className = beforeClass(description); String whipStaticInstrument = System.getProperty( "whip.static.instrument"); System.setProperty("whip.static.instrument", StringPool.TRUE); try { statement.evaluate(); } finally { afterClass(description, className); if (whipStaticInstrument == null) { System.clearProperty("whip.static.instrument"); } else { System.setProperty( "whip.static.instrument", whipStaticInstrument); } } } }; } protected void afterClass(Description description, String className) throws Throwable { List<Class<?>> assertClasses = new ArrayList<>(); if (className != null) { ClassLoader classLoader = getClassLoader(); Class<?> clazz = classLoader.loadClass(className); assertClasses.add(clazz); } appendAssertClasses(assertClasses); try { _ASSERT_COVERAGE_METHOD.invoke( null, _includeInnerClasses, assertClasses.toArray(new Class<?>[assertClasses.size()])); } catch (InvocationTargetException ite) { throw ite.getCause(); } } protected String beforeClass(Description description) throws Throwable { String className = description.getClassName(); if (className.endsWith("Test")) { className = className.substring(0, className.length() - 4); } String jvmClassPath = ClassPathUtil.getJVMClassPath(false); URL[] urls = ClassPathUtil.getClassPathURLs(jvmClassPath); ClassLoader classLoader = new URLClassLoader(urls, null); try { classLoader.loadClass(className); } catch (ClassNotFoundException cnfe) { className = null; } String[] includes = _includes; if (includes == null) { includes = _generateIncludes(classLoader, className); } try { _DYNAMICALLY_INSTRUMENT_METHOD.invoke(null, includes, _excludes); } catch (InvocationTargetException ite) { throw ite.getCause(); } return className; } protected ClassLoader getClassLoader() { Class<?> clazz = getClass(); return clazz.getClassLoader(); } private String[] _generateIncludes( ClassLoader classLoader, String mainClassName) throws Exception { List<Class<?>> assertClasses = new ArrayList<>(); if (mainClassName != null) { Class<?> mainClass = classLoader.loadClass(mainClassName); assertClasses.add(mainClass); if (_includeInnerClasses) { Collections.addAll( assertClasses, mainClass.getDeclaredClasses()); } } if (getClass() != CodeCoverageAssertor.class) { Class<?> clazz = getClass(); Class<?> reloadedClass = classLoader.loadClass(clazz.getName()); Method appendAssertClassesMethod = reloadedClass.getMethod( "appendAssertClasses", List.class); appendAssertClassesMethod.setAccessible(true); Constructor<?> constructor = reloadedClass.getDeclaredConstructor(); constructor.setAccessible(true); Object reloadedObject = constructor.newInstance(); appendAssertClassesMethod.invoke(reloadedObject, assertClasses); } String[] includes = new String[assertClasses.size()]; for (int i = 0; i < assertClasses.size(); i++) { Class<?> assertClass = assertClasses.get(i); includes[i] = StringUtil.replace( assertClass.getName(), new char[] {'.', '$'}, new String[] {"/", "\\$"}); } return includes; } private static final Method _ASSERT_COVERAGE_METHOD; private static final Method _DYNAMICALLY_INSTRUMENT_METHOD; static { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); try { Class<?> instrumentationAgentClass = systemClassLoader.loadClass( "com.liferay.whip.agent.InstrumentationAgent"); _ASSERT_COVERAGE_METHOD = instrumentationAgentClass.getMethod( "assertCoverage", boolean.class, Class[].class); _DYNAMICALLY_INSTRUMENT_METHOD = instrumentationAgentClass.getMethod( "dynamicallyInstrument", String[].class, String[].class); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } private final String[] _excludes; private final boolean _includeInnerClasses; private final String[] _includes; }