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.StringPool;
019    import com.liferay.portal.kernel.util.StringUtil;
020    
021    import java.lang.reflect.Constructor;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    
025    import java.net.URL;
026    import java.net.URLClassLoader;
027    
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.List;
031    
032    import org.junit.rules.TestRule;
033    import org.junit.runner.Description;
034    import org.junit.runners.model.Statement;
035    
036    /**
037     * @author Shuyang Zhou
038     */
039    public class CodeCoverageAssertor implements TestRule {
040    
041            public static final CodeCoverageAssertor INSTANCE =
042                    new CodeCoverageAssertor();
043    
044            public CodeCoverageAssertor() {
045                    this(null, null, true);
046            }
047    
048            public CodeCoverageAssertor(
049                    String[] includes, String[] excludes, boolean includeInnerClasses) {
050    
051                    _includes = includes;
052                    _excludes = excludes;
053                    _includeInnerClasses = includeInnerClasses;
054            }
055    
056            public void appendAssertClasses(List<Class<?>> assertClasses) {
057            }
058    
059            @Override
060            public Statement apply(
061                    final Statement statement, final Description description) {
062    
063                    if (description.getMethodName() != null) {
064                            return statement;
065                    }
066    
067                    return new Statement() {
068    
069                            @Override
070                            public void evaluate() throws Throwable {
071                                    String className = beforeClass(description);
072    
073                                    String whipStaticInstrument = System.getProperty(
074                                            "whip.static.instrument");
075    
076                                    System.setProperty("whip.static.instrument", StringPool.TRUE);
077    
078                                    try {
079                                            statement.evaluate();
080                                    }
081                                    finally {
082                                            afterClass(description, className);
083    
084                                            if (whipStaticInstrument == null) {
085                                                    System.clearProperty("whip.static.instrument");
086                                            }
087                                            else {
088                                                    System.setProperty(
089                                                            "whip.static.instrument", whipStaticInstrument);
090                                            }
091                                    }
092                            }
093    
094                    };
095            }
096    
097            protected void afterClass(Description description, String className)
098                    throws Throwable {
099    
100                    List<Class<?>> assertClasses = new ArrayList<>();
101    
102                    if (className != null) {
103                            ClassLoader classLoader = getClassLoader();
104    
105                            Class<?> clazz = classLoader.loadClass(className);
106    
107                            assertClasses.add(clazz);
108                    }
109    
110                    appendAssertClasses(assertClasses);
111    
112                    try {
113                            _ASSERT_COVERAGE_METHOD.invoke(
114                                    null, _includeInnerClasses,
115                                    assertClasses.toArray(new Class<?>[assertClasses.size()]));
116                    }
117                    catch (InvocationTargetException ite) {
118                            throw ite.getCause();
119                    }
120            }
121    
122            protected String beforeClass(Description description) throws Throwable {
123                    String className = description.getClassName();
124    
125                    if (className.endsWith("Test")) {
126                            className = className.substring(0, className.length() - 4);
127                    }
128    
129                    String jvmClassPath = ClassPathUtil.getJVMClassPath(false);
130    
131                    URL[] urls = ClassPathUtil.getClassPathURLs(jvmClassPath);
132    
133                    ClassLoader classLoader = new URLClassLoader(urls, null);
134    
135                    try {
136                            classLoader.loadClass(className);
137                    }
138                    catch (ClassNotFoundException cnfe) {
139                            className = null;
140                    }
141    
142                    String[] includes = _includes;
143    
144                    if (includes == null) {
145                            includes = _generateIncludes(classLoader, className);
146                    }
147    
148                    try {
149                            _DYNAMICALLY_INSTRUMENT_METHOD.invoke(null, includes, _excludes);
150                    }
151                    catch (InvocationTargetException ite) {
152                            throw ite.getCause();
153                    }
154    
155                    return className;
156            }
157    
158            protected ClassLoader getClassLoader() {
159                    Class<?> clazz = getClass();
160    
161                    return clazz.getClassLoader();
162            }
163    
164            private String[] _generateIncludes(
165                            ClassLoader classLoader, String mainClassName)
166                    throws Exception {
167    
168                    List<Class<?>> assertClasses = new ArrayList<>();
169    
170                    if (mainClassName != null) {
171                            Class<?> mainClass = classLoader.loadClass(mainClassName);
172    
173                            assertClasses.add(mainClass);
174    
175                            if (_includeInnerClasses) {
176                                    assertClasses.addAll(
177                                            Arrays.asList(mainClass.getDeclaredClasses()));
178                            }
179                    }
180    
181                    if (getClass() != CodeCoverageAssertor.class) {
182                            Class<?> clazz = getClass();
183    
184                            Class<?> reloadedClass = classLoader.loadClass(clazz.getName());
185    
186                            Method appendAssertClassesMethod = reloadedClass.getMethod(
187                                    "appendAssertClasses", List.class);
188    
189                            appendAssertClassesMethod.setAccessible(true);
190    
191                            Constructor<?> constructor = reloadedClass.getDeclaredConstructor();
192    
193                            constructor.setAccessible(true);
194    
195                            Object reloadedObject = constructor.newInstance();
196    
197                            appendAssertClassesMethod.invoke(reloadedObject, assertClasses);
198                    }
199    
200                    String[] includes = new String[assertClasses.size()];
201    
202                    for (int i = 0; i < assertClasses.size(); i++) {
203                            Class<?> assertClass = assertClasses.get(i);
204    
205                            includes[i] = StringUtil.replace(
206                                    assertClass.getName(), new char[] {'.', '$'},
207                                    new String[] {"/", "\\$"});
208                    }
209    
210                    return includes;
211            }
212    
213            private static final Method _ASSERT_COVERAGE_METHOD;
214    
215            private static final Method _DYNAMICALLY_INSTRUMENT_METHOD;
216    
217            static {
218                    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
219    
220                    try {
221                            Class<?> instrumentationAgentClass = systemClassLoader.loadClass(
222                                    "com.liferay.whip.agent.InstrumentationAgent");
223    
224                            _ASSERT_COVERAGE_METHOD = instrumentationAgentClass.getMethod(
225                                    "assertCoverage", boolean.class, Class[].class);
226                            _DYNAMICALLY_INSTRUMENT_METHOD =
227                                    instrumentationAgentClass.getMethod(
228                                            "dynamicallyInstrument", String[].class, String[].class);
229                    }
230                    catch (Exception e) {
231                            throw new ExceptionInInitializerError(e);
232                    }
233            }
234    
235            private final String[] _excludes;
236            private final boolean _includeInnerClasses;
237            private final String[] _includes;
238    
239    }