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;
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.Method;
022    
023    import java.net.URL;
024    import java.net.URLClassLoader;
025    
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Iterator;
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 CodeCoverageAssertor() {
041                    this(null, null, true);
042            }
043    
044            public CodeCoverageAssertor(
045                    String[] includes, String[] excludes, boolean includeInnerClasses) {
046    
047                    _includes = includes;
048                    _excludes = excludes;
049                    _includeInnerClasses = includeInnerClasses;
050            }
051    
052            public void appendAssertClasses(List<Class<?>> assertClasses) {
053            }
054    
055            @Override
056            public Statement apply(
057                    final Statement statement, final Description description) {
058    
059                    return new Statement() {
060    
061                            @Override
062                            public void evaluate() throws Throwable {
063                                    String className = description.getClassName();
064    
065                                    if (className.endsWith("Test")) {
066                                            className = className.substring(0, className.length() - 4);
067                                    }
068    
069                                    String[] includes = _includes;
070    
071                                    if (includes == null) {
072                                            includes = _generateIncludes(className);
073                                    }
074    
075                                    _DYNAMICALLY_INSTRUMENT_METHOD.invoke(
076                                            null, includes, _excludes);
077    
078                                    try {
079                                            statement.evaluate();
080                                    }
081                                    finally {
082                                            List<Class<?>> assertClasses = new ArrayList<Class<?>>();
083    
084                                            ClassLoader classLoader = getClassLoader();
085    
086                                            Class<?> clazz = classLoader.loadClass(className);
087    
088                                            assertClasses.add(clazz);
089    
090                                            appendAssertClasses(assertClasses);
091    
092                                            _purgeSyntheticClasses(assertClasses);
093    
094                                            _ASSERT_COVERAGE_METHOD.invoke(
095                                                    null, _includeInnerClasses,
096                                                    assertClasses.toArray(
097                                                            new Class<?>[assertClasses.size()]));
098                                    }
099                            }
100    
101                    };
102            }
103    
104            protected ClassLoader getClassLoader() {
105                    Class<?> clazz = getClass();
106    
107                    return clazz.getClassLoader();
108            }
109    
110            private static void _purgeSyntheticClasses(List<Class<?>> assertClasses) {
111                    Iterator<Class<?>> iterator = assertClasses.iterator();
112    
113                    while (iterator.hasNext()) {
114                            Class<?> assertClass = iterator.next();
115    
116                            if (assertClass.isSynthetic()) {
117                                    iterator.remove();
118                            }
119                    }
120            }
121    
122            private String[] _generateIncludes(String mainClassName) throws Exception {
123                    List<Class<?>> assertClasses = new ArrayList<Class<?>>();
124    
125                    String jvmClassPath = ClassPathUtil.getJVMClassPath(false);
126    
127                    URL[] urls = ClassPathUtil.getClassPathURLs(jvmClassPath);
128    
129                    ClassLoader classLoader = new URLClassLoader(urls, null);
130    
131                    Class<?> mainClass = classLoader.loadClass(mainClassName);
132    
133                    assertClasses.add(mainClass);
134    
135                    if (_includeInnerClasses) {
136                            assertClasses.addAll(Arrays.asList(mainClass.getDeclaredClasses()));
137                    }
138    
139                    if (getClass() != CodeCoverageAssertor.class) {
140                            Class<?> reloadedClass = classLoader.loadClass(
141                                    getClass().getName());
142    
143                            Method appendAssertClassesMethod = reloadedClass.getMethod(
144                                    "appendAssertClasses", List.class);
145    
146                            appendAssertClassesMethod.setAccessible(true);
147    
148                            Constructor<?> constructor = reloadedClass.getDeclaredConstructor();
149    
150                            constructor.setAccessible(true);
151    
152                            Object reloadedObject = constructor.newInstance();
153    
154                            appendAssertClassesMethod.invoke(reloadedObject, assertClasses);
155                    }
156    
157                    _purgeSyntheticClasses(assertClasses);
158    
159                    String[] includes = new String[assertClasses.size()];
160    
161                    for (int i = 0; i < assertClasses.size(); i++) {
162                            Class<?> assertClass = assertClasses.get(i);
163    
164                            includes[i] = StringUtil.replace(
165                                    assertClass.getName(), new String[] {".", "$"},
166                                    new String[] {"/", "\\$"});
167                    }
168    
169                    return includes;
170            }
171    
172            private static final Method _ASSERT_COVERAGE_METHOD;
173    
174            private static final Method _DYNAMICALLY_INSTRUMENT_METHOD;
175    
176            static {
177                    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
178    
179                    try {
180                            Class<?> instrumentationAgentClass = systemClassLoader.loadClass(
181                                    "com.liferay.cobertura.instrument.InstrumentationAgent");
182    
183                            _ASSERT_COVERAGE_METHOD = instrumentationAgentClass.getMethod(
184                                    "assertCoverage", boolean.class, Class[].class);
185                            _DYNAMICALLY_INSTRUMENT_METHOD =
186                                    instrumentationAgentClass.getMethod(
187                                            "dynamicallyInstrument", String[].class, String[].class);
188                    }
189                    catch (Exception e) {
190                            throw new ExceptionInInitializerError(e);
191                    }
192            }
193    
194            private final String[] _excludes;
195            private final boolean _includeInnerClasses;
196            private final String[] _includes;
197    
198    }