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.process.ProcessCallable;
019    import com.liferay.portal.kernel.process.ProcessChannel;
020    import com.liferay.portal.kernel.process.ProcessConfig;
021    import com.liferay.portal.kernel.process.ProcessConfig.Builder;
022    import com.liferay.portal.kernel.process.ProcessException;
023    import com.liferay.portal.kernel.process.ProcessExecutor;
024    import com.liferay.portal.kernel.process.local.LocalProcessExecutor;
025    import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ProcessContext;
026    import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ShutdownHook;
027    import com.liferay.portal.kernel.test.BaseTestRule.StatementWrapper;
028    import com.liferay.portal.kernel.util.MethodCache;
029    import com.liferay.portal.kernel.util.MethodKey;
030    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
031    import com.liferay.portal.kernel.util.StringBundler;
032    import com.liferay.portal.kernel.util.StringPool;
033    import com.liferay.portal.kernel.util.SystemProperties;
034    import com.liferay.portal.kernel.util.Validator;
035    
036    import java.io.Serializable;
037    
038    import java.lang.annotation.Annotation;
039    import java.lang.reflect.InvocationTargetException;
040    import java.lang.reflect.Method;
041    
042    import java.net.MalformedURLException;
043    import java.net.URLClassLoader;
044    
045    import java.util.ArrayList;
046    import java.util.List;
047    import java.util.concurrent.ExecutionException;
048    import java.util.concurrent.Future;
049    
050    import org.junit.After;
051    import org.junit.Before;
052    import org.junit.rules.TestRule;
053    import org.junit.runner.Description;
054    import org.junit.runners.model.FrameworkMethod;
055    import org.junit.runners.model.Statement;
056    import org.junit.runners.model.TestClass;
057    
058    /**
059     * @author Shuyang Zhou
060     */
061    public class NewEnvTestRule implements TestRule {
062    
063            public static final NewEnvTestRule INSTANCE = new NewEnvTestRule();
064    
065            @Override
066            public Statement apply(Statement statement, Description description) {
067                    String methodName = description.getMethodName();
068    
069                    if (methodName == null) {
070                            return statement;
071                    }
072    
073                    NewEnv newEnv = findNewEnv(description);
074    
075                    if ((newEnv == null) || (newEnv.type() == NewEnv.Type.NONE)) {
076                            return statement;
077                    }
078    
079                    if (NewEnv.Type.CLASSLOADER == newEnv.type()) {
080                            return new RunInNewClassLoaderStatement(statement, description);
081                    }
082    
083                    Builder builder = new Builder();
084    
085                    builder.setArguments(createArguments(description));
086                    builder.setBootstrapClassPath(CLASS_PATH);
087                    builder.setRuntimeClassPath(CLASS_PATH);
088    
089                    return new RunInNewJVMStatment(builder.build(), statement, description);
090            }
091    
092            protected static void attachProcess(String message) {
093                    if (Boolean.getBoolean("attached")) {
094                            return;
095                    }
096    
097                    ProcessContext.attach(
098                            message, 1000,
099                            new ShutdownHook() {
100    
101                                    @Override
102                                    public boolean shutdown(
103                                            int shutdownCode, Throwable shutdownThrowable) {
104    
105                                            System.exit(shutdownCode);
106    
107                                            return true;
108                                    }
109    
110                            });
111    
112                    System.setProperty("attached", StringPool.TRUE);
113            }
114    
115            protected static List<MethodKey> getMethodKeys(
116                    Class<?> targetClass, Class<? extends Annotation> annotationClass) {
117    
118                    TestClass testClass = new TestClass(targetClass);
119    
120                    List<FrameworkMethod> frameworkMethods = testClass.getAnnotatedMethods(
121                            annotationClass);
122    
123                    List<MethodKey> methodKeys = new ArrayList<MethodKey>(
124                            frameworkMethods.size());
125    
126                    for (FrameworkMethod annotatedFrameworkMethod : frameworkMethods) {
127                            methodKeys.add(new MethodKey(annotatedFrameworkMethod.getMethod()));
128                    }
129    
130                    return methodKeys;
131            }
132    
133            protected static void invoke(
134                            ClassLoader classLoader, MethodKey methodKey, Object object)
135                    throws Exception {
136    
137                    methodKey = methodKey.transform(classLoader);
138    
139                    Method method = methodKey.getMethod();
140    
141                    method.invoke(object);
142            }
143    
144            protected NewEnvTestRule() {
145            }
146    
147            protected List<String> createArguments(Description description) {
148                    List<String> arguments = new ArrayList<String>();
149    
150                    if (Boolean.getBoolean("junit.debug")) {
151                            arguments.add(_JPDA_OPTIONS);
152                            arguments.add("-Djunit.debug=true");
153                    }
154    
155                    String agentLine = System.getProperty("junit.cobertura.agent");
156    
157                    if (Validator.isNotNull(agentLine)) {
158                            arguments.add(agentLine);
159                            arguments.add("-Djunit.cobertura.agent=" + agentLine);
160                    }
161    
162                    if (Boolean.getBoolean("junit.code.coverage")) {
163                            arguments.add("-Djunit.code.coverage=true");
164                    }
165    
166                    if (Boolean.getBoolean("junit.code.coverage.dump")) {
167                            arguments.add("-Djunit.code.coverage.dump=true");
168                    }
169    
170                    arguments.add("-Djava.net.preferIPv4Stack=true");
171    
172                    String fileName = System.getProperty(
173                            "net.sourceforge.cobertura.datafile");
174    
175                    if (fileName != null) {
176                            arguments.add("-Dnet.sourceforge.cobertura.datafile=" + fileName);
177                    }
178    
179                    return arguments;
180            }
181    
182            protected ClassLoader createClassLoader(Description description) {
183                    try {
184                            return new URLClassLoader(
185                                    ClassPathUtil.getClassPathURLs(CLASS_PATH), null);
186                    }
187                    catch (MalformedURLException murle) {
188                            throw new RuntimeException(murle);
189                    }
190            }
191    
192            protected NewEnv findNewEnv(Description description) {
193                    NewEnv newEnv = description.getAnnotation(NewEnv.class);
194    
195                    if (newEnv == null) {
196                            Class<?> testClass = description.getTestClass();
197    
198                            newEnv = testClass.getAnnotation(NewEnv.class);
199                    }
200    
201                    return newEnv;
202            }
203    
204            protected ProcessCallable<Serializable> processProcessCallable(
205                    ProcessCallable<Serializable> processCallable,
206                    MethodKey testMethodKey) {
207    
208                    return processCallable;
209            }
210    
211            protected static final String CLASS_PATH = ClassPathUtil.getJVMClassPath(
212                    true);
213    
214            private static final String _JPDA_OPTIONS =
215                    "-agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=y";
216    
217            private static final ProcessExecutor _processExecutor =
218                    new LocalProcessExecutor();
219    
220            static {
221                    Thread currentThread = Thread.currentThread();
222    
223                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
224    
225                    PortalClassLoaderUtil.setClassLoader(contextClassLoader);
226            }
227    
228            private static class TestProcessCallable
229                    implements ProcessCallable<Serializable> {
230    
231                    public TestProcessCallable(
232                            String testClassName, List<MethodKey> beforeMethodKeys,
233                            MethodKey testMethodKey, List<MethodKey> afterMethodKeys) {
234    
235                            _testClassName = testClassName;
236                            _beforeMethodKeys = beforeMethodKeys;
237                            _testMethodKey = testMethodKey;
238                            _afterMethodKeys = afterMethodKeys;
239                    }
240    
241                    @Override
242                    public Serializable call() throws ProcessException {
243                            attachProcess("Attached " + toString());
244    
245                            Thread currentThread = Thread.currentThread();
246    
247                            ClassLoader contextClassLoader =
248                                    currentThread.getContextClassLoader();
249    
250                            System.setProperty(
251                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
252    
253                            try {
254                                    Class<?> clazz = contextClassLoader.loadClass(_testClassName);
255    
256                                    Object object = clazz.newInstance();
257    
258                                    for (MethodKey beforeMethodKey : _beforeMethodKeys) {
259                                            invoke(contextClassLoader, beforeMethodKey, object);
260                                    }
261    
262                                    invoke(contextClassLoader, _testMethodKey, object);
263    
264                                    for (MethodKey afterMethodKey : _afterMethodKeys) {
265                                            invoke(contextClassLoader, afterMethodKey, object);
266                                    }
267                            }
268                            catch (Exception e) {
269                                    throw new ProcessException(e);
270                            }
271    
272                            return StringPool.BLANK;
273                    }
274    
275                    @Override
276                    public String toString() {
277                            StringBundler sb = new StringBundler(4);
278    
279                            sb.append(_testClassName);
280                            sb.append(StringPool.PERIOD);
281                            sb.append(_testMethodKey.getMethodName());
282                            sb.append("()");
283    
284                            return sb.toString();
285                    }
286    
287                    private static final long serialVersionUID = 1L;
288    
289                    private final List<MethodKey> _afterMethodKeys;
290                    private final List<MethodKey> _beforeMethodKeys;
291                    private final String _testClassName;
292                    private final MethodKey _testMethodKey;
293    
294            }
295    
296            private class RunInNewClassLoaderStatement extends StatementWrapper {
297    
298                    public RunInNewClassLoaderStatement(
299                            Statement statement, Description description) {
300    
301                            super(statement);
302    
303                            Class<?> testClass = description.getTestClass();
304    
305                            _afterMethodKeys = getMethodKeys(testClass, After.class);
306                            _beforeMethodKeys = getMethodKeys(testClass, Before.class);
307                            _newClassLoader = createClassLoader(description);
308                            _testClassName = testClass.getName();
309                            _testMethodKey = new MethodKey(
310                                    testClass, description.getMethodName());
311                    }
312    
313                    @Override
314                    public void evaluate() throws Throwable {
315                            MethodCache.reset();
316    
317                            Thread currentThread = Thread.currentThread();
318    
319                            ClassLoader contextClassLoader =
320                                    currentThread.getContextClassLoader();
321    
322                            currentThread.setContextClassLoader(_newClassLoader);
323    
324                            String quiet = System.getProperty(
325                                    SystemProperties.SYSTEM_PROPERTIES_QUIET);
326    
327                            System.setProperty(
328                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
329    
330                            try {
331                                    Class<?> clazz = _newClassLoader.loadClass(_testClassName);
332    
333                                    Object object = clazz.newInstance();
334    
335                                    for (MethodKey beforeMethodKey : _beforeMethodKeys) {
336                                            invoke(_newClassLoader, beforeMethodKey, object);
337                                    }
338    
339                                    invoke(_newClassLoader, _testMethodKey, object);
340    
341                                    for (MethodKey afterMethodKey : _afterMethodKeys) {
342                                            invoke(_newClassLoader, afterMethodKey, object);
343                                    }
344                            }
345                            catch (InvocationTargetException ite) {
346                                    throw ite.getTargetException();
347                            }
348                            finally {
349                                    if (quiet == null) {
350                                            System.clearProperty(
351                                                    SystemProperties.SYSTEM_PROPERTIES_QUIET);
352                                    }
353                                    else {
354                                            System.setProperty(
355                                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, quiet);
356                                    }
357    
358                                    currentThread.setContextClassLoader(contextClassLoader);
359    
360                                    MethodCache.reset();
361                            }
362                    }
363    
364                    private final List<MethodKey> _afterMethodKeys;
365                    private final List<MethodKey> _beforeMethodKeys;
366                    private final ClassLoader _newClassLoader;
367                    private final String _testClassName;
368                    private final MethodKey _testMethodKey;
369    
370            }
371    
372            private class RunInNewJVMStatment extends StatementWrapper {
373    
374                    public RunInNewJVMStatment(
375                            ProcessConfig processConfig, Statement statement,
376                            Description description) {
377    
378                            super(statement);
379    
380                            _processConfig = processConfig;
381    
382                            Class<?> testClass = description.getTestClass();
383    
384                            _afterMethodKeys = getMethodKeys(testClass, After.class);
385                            _beforeMethodKeys = getMethodKeys(testClass, Before.class);
386                            _testClassName = testClass.getName();
387                            _testMethodKey = new MethodKey(
388                                    testClass, description.getMethodName());
389                    }
390    
391                    @Override
392                    public void evaluate() throws Throwable {
393                            ProcessCallable<Serializable> processCallable =
394                                    new TestProcessCallable(
395                                            _testClassName, _beforeMethodKeys, _testMethodKey,
396                                            _afterMethodKeys);
397    
398                            processCallable = processProcessCallable(
399                                    processCallable, _testMethodKey);
400    
401                            ProcessChannel<Serializable> processChannel =
402                                    _processExecutor.execute(_processConfig, processCallable);
403    
404                            Future<Serializable> future =
405                                    processChannel.getProcessNoticeableFuture();
406    
407                            try {
408                                    future.get();
409                            }
410                            catch (ExecutionException ee) {
411                                    Throwable cause = ee.getCause();
412    
413                                    while ((cause instanceof ProcessException) ||
414                                               (cause instanceof InvocationTargetException)) {
415    
416                                            cause = cause.getCause();
417                                    }
418    
419                                    throw cause;
420                            }
421                    }
422    
423                    private final List<MethodKey> _afterMethodKeys;
424                    private final List<MethodKey> _beforeMethodKeys;
425                    private final ProcessConfig _processConfig;
426                    private final String _testClassName;
427                    private final MethodKey _testMethodKey;
428    
429            }
430    
431    }