001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.test.rule;
016    
017    import com.liferay.portal.kernel.process.ClassPathUtil;
018    import com.liferay.portal.kernel.util.StringUtil;
019    
020    import java.lang.reflect.Constructor;
021    import java.lang.reflect.InvocationTargetException;
022    import java.lang.reflect.Method;
023    
024    import java.net.URL;
025    import java.net.URLClassLoader;
026    
027    import java.util.ArrayList;
028    import java.util.Arrays;
029    import java.util.List;
030    
031    import org.junit.rules.TestRule;
032    import org.junit.runner.Description;
033    import org.junit.runners.model.Statement;
034    
035    /**
036     * @author Shuyang Zhou
037     */
038    public class CodeCoverageAssertor implements TestRule {
039    
040            public static final CodeCoverageAssertor INSTANCE =
041                    new CodeCoverageAssertor();
042    
043            public CodeCoverageAssertor() {
044                    this(null, null, true);
045            }
046    
047            public CodeCoverageAssertor(
048                    String[] includes, String[] excludes, boolean includeInnerClasses) {
049    
050                    _includes = includes;
051                    _excludes = excludes;
052                    _includeInnerClasses = includeInnerClasses;
053            }
054    
055            public void appendAssertClasses(List<Class<?>> assertClasses) {
056            }
057    
058            @Override
059            public Statement apply(
060                    final Statement statement, final Description description) {
061    
062                    if (description.getMethodName() != null) {
063                            return statement;
064                    }
065    
066                    return new Statement() {
067    
068                            @Override
069                            public void evaluate() throws Throwable {
070                                    String className = beforeClass(description);
071    
072                                    try {
073                                            statement.evaluate();
074                                    }
075                                    finally {
076                                            afterClass(description, className);
077                                    }
078                            }
079    
080                    };
081            }
082    
083            protected void afterClass(Description description, String className)
084                    throws Throwable {
085    
086                    List<Class<?>> assertClasses = new ArrayList<>();
087    
088                    ClassLoader classLoader = getClassLoader();
089    
090                    Class<?> clazz = classLoader.loadClass(className);
091    
092                    assertClasses.add(clazz);
093    
094                    appendAssertClasses(assertClasses);
095    
096                    try {
097                            _ASSERT_COVERAGE_METHOD.invoke(
098                                    null, _includeInnerClasses,
099                                    assertClasses.toArray(new Class<?>[assertClasses.size()]));
100                    }
101                    catch (InvocationTargetException ite) {
102                            throw ite.getCause();
103                    }
104            }
105    
106            protected String beforeClass(Description description) throws Throwable {
107                    String className = description.getClassName();
108    
109                    if (className.endsWith("Test")) {
110                            className = className.substring(0, className.length() - 4);
111                    }
112    
113                    String[] includes = _includes;
114    
115                    if (includes == null) {
116                            includes = _generateIncludes(className);
117                    }
118    
119                    try {
120                            _DYNAMICALLY_INSTRUMENT_METHOD.invoke(null, includes, _excludes);
121                    }
122                    catch (InvocationTargetException ite) {
123                            throw ite.getCause();
124                    }
125    
126                    return className;
127            }
128    
129            protected ClassLoader getClassLoader() {
130                    Class<?> clazz = getClass();
131    
132                    return clazz.getClassLoader();
133            }
134    
135            private String[] _generateIncludes(String mainClassName) throws Exception {
136                    List<Class<?>> assertClasses = new ArrayList<>();
137    
138                    String jvmClassPath = ClassPathUtil.getJVMClassPath(false);
139    
140                    URL[] urls = ClassPathUtil.getClassPathURLs(jvmClassPath);
141    
142                    ClassLoader classLoader = new URLClassLoader(urls, null);
143    
144                    Class<?> mainClass = classLoader.loadClass(mainClassName);
145    
146                    assertClasses.add(mainClass);
147    
148                    if (_includeInnerClasses) {
149                            assertClasses.addAll(Arrays.asList(mainClass.getDeclaredClasses()));
150                    }
151    
152                    if (getClass() != CodeCoverageAssertor.class) {
153                            Class<?> reloadedClass = classLoader.loadClass(
154                                    getClass().getName());
155    
156                            Method appendAssertClassesMethod = reloadedClass.getMethod(
157                                    "appendAssertClasses", List.class);
158    
159                            appendAssertClassesMethod.setAccessible(true);
160    
161                            Constructor<?> constructor = reloadedClass.getDeclaredConstructor();
162    
163                            constructor.setAccessible(true);
164    
165                            Object reloadedObject = constructor.newInstance();
166    
167                            appendAssertClassesMethod.invoke(reloadedObject, assertClasses);
168                    }
169    
170                    String[] includes = new String[assertClasses.size()];
171    
172                    for (int i = 0; i < assertClasses.size(); i++) {
173                            Class<?> assertClass = assertClasses.get(i);
174    
175                            includes[i] = StringUtil.replace(
176                                    assertClass.getName(), new String[] {".", "$"},
177                                    new String[] {"/", "\\$"});
178                    }
179    
180                    return includes;
181            }
182    
183            private static final Method _ASSERT_COVERAGE_METHOD;
184    
185            private static final Method _DYNAMICALLY_INSTRUMENT_METHOD;
186    
187            static {
188                    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
189    
190                    try {
191                            Class<?> instrumentationAgentClass = systemClassLoader.loadClass(
192                                    "com.liferay.whip.agent.InstrumentationAgent");
193    
194                            _ASSERT_COVERAGE_METHOD = instrumentationAgentClass.getMethod(
195                                    "assertCoverage", boolean.class, Class[].class);
196                            _DYNAMICALLY_INSTRUMENT_METHOD =
197                                    instrumentationAgentClass.getMethod(
198                                            "dynamicallyInstrument", String[].class, String[].class);
199                    }
200                    catch (Exception e) {
201                            throw new ExceptionInInitializerError(e);
202                    }
203            }
204    
205            private final String[] _excludes;
206            private final boolean _includeInnerClasses;
207            private final String[] _includes;
208    
209    }