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.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.rule.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<>(frameworkMethods.size());
124    
125                    for (FrameworkMethod annotatedFrameworkMethod : frameworkMethods) {
126                            methodKeys.add(new MethodKey(annotatedFrameworkMethod.getMethod()));
127                    }
128    
129                    return methodKeys;
130            }
131    
132            protected static void invoke(
133                            ClassLoader classLoader, MethodKey methodKey, Object object)
134                    throws Exception {
135    
136                    methodKey = methodKey.transform(classLoader);
137    
138                    Method method = methodKey.getMethod();
139    
140                    method.invoke(object);
141            }
142    
143            protected NewEnvTestRule() {
144            }
145    
146            protected List<String> createArguments(Description description) {
147                    List<String> arguments = new ArrayList<>();
148    
149                    arguments.add("-Djava.net.preferIPv4Stack=true");
150    
151                    if (Boolean.getBoolean("junit.debug")) {
152                            arguments.add(_JPDA_OPTIONS);
153                            arguments.add("-Djunit.debug=true");
154                    }
155    
156                    arguments.add("-Dliferay.mode=test");
157    
158                    String whipAgentLine = System.getProperty("whip.agent");
159    
160                    if (Validator.isNotNull(whipAgentLine)) {
161                            arguments.add(whipAgentLine);
162                            arguments.add("-Dwhip.agent=" + whipAgentLine);
163                    }
164    
165                    String fileName = System.getProperty("whip.datafile");
166    
167                    if (fileName != null) {
168                            arguments.add("-Dwhip.datafile=" + fileName);
169                    }
170    
171                    if (Boolean.getBoolean("whip.instrument.dump")) {
172                            arguments.add("-Dwhip.instrument.dump=true");
173                    }
174    
175                    arguments.add("-Dwhip.static.instrument=true");
176    
177                    return arguments;
178            }
179    
180            protected ClassLoader createClassLoader(Description description) {
181                    try {
182                            return new URLClassLoader(
183                                    ClassPathUtil.getClassPathURLs(CLASS_PATH), null);
184                    }
185                    catch (MalformedURLException murle) {
186                            throw new RuntimeException(murle);
187                    }
188            }
189    
190            protected NewEnv findNewEnv(Description description) {
191                    NewEnv newEnv = description.getAnnotation(NewEnv.class);
192    
193                    if (newEnv == null) {
194                            Class<?> testClass = description.getTestClass();
195    
196                            newEnv = testClass.getAnnotation(NewEnv.class);
197                    }
198    
199                    return newEnv;
200            }
201    
202            protected ProcessCallable<Serializable> processProcessCallable(
203                    ProcessCallable<Serializable> processCallable,
204                    MethodKey testMethodKey) {
205    
206                    return processCallable;
207            }
208    
209            protected static final String CLASS_PATH = ClassPathUtil.getJVMClassPath(
210                    true);
211    
212            private static final String _JPDA_OPTIONS =
213                    "-agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=y";
214    
215            private static final ProcessExecutor _processExecutor =
216                    new LocalProcessExecutor();
217    
218            static {
219                    Thread currentThread = Thread.currentThread();
220    
221                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
222    
223                    PortalClassLoaderUtil.setClassLoader(contextClassLoader);
224            }
225    
226            private static class TestProcessCallable
227                    implements ProcessCallable<Serializable> {
228    
229                    public TestProcessCallable(
230                            String testClassName, List<MethodKey> beforeMethodKeys,
231                            MethodKey testMethodKey, List<MethodKey> afterMethodKeys) {
232    
233                            _testClassName = testClassName;
234                            _beforeMethodKeys = beforeMethodKeys;
235                            _testMethodKey = testMethodKey;
236                            _afterMethodKeys = afterMethodKeys;
237                    }
238    
239                    @Override
240                    public Serializable call() throws ProcessException {
241                            attachProcess("Attached " + toString());
242    
243                            Thread currentThread = Thread.currentThread();
244    
245                            ClassLoader contextClassLoader =
246                                    currentThread.getContextClassLoader();
247    
248                            System.setProperty(
249                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
250    
251                            try {
252                                    Class<?> clazz = contextClassLoader.loadClass(_testClassName);
253    
254                                    Object object = clazz.newInstance();
255    
256                                    for (MethodKey beforeMethodKey : _beforeMethodKeys) {
257                                            invoke(contextClassLoader, beforeMethodKey, object);
258                                    }
259    
260                                    invoke(contextClassLoader, _testMethodKey, object);
261    
262                                    for (MethodKey afterMethodKey : _afterMethodKeys) {
263                                            invoke(contextClassLoader, afterMethodKey, object);
264                                    }
265                            }
266                            catch (Exception e) {
267                                    throw new ProcessException(e);
268                            }
269    
270                            return StringPool.BLANK;
271                    }
272    
273                    @Override
274                    public String toString() {
275                            StringBundler sb = new StringBundler(4);
276    
277                            sb.append(_testClassName);
278                            sb.append(StringPool.PERIOD);
279                            sb.append(_testMethodKey.getMethodName());
280                            sb.append("()");
281    
282                            return sb.toString();
283                    }
284    
285                    private static final long serialVersionUID = 1L;
286    
287                    private final List<MethodKey> _afterMethodKeys;
288                    private final List<MethodKey> _beforeMethodKeys;
289                    private final String _testClassName;
290                    private final MethodKey _testMethodKey;
291    
292            }
293    
294            private class RunInNewClassLoaderStatement extends StatementWrapper {
295    
296                    public RunInNewClassLoaderStatement(
297                            Statement statement, Description description) {
298    
299                            super(statement);
300    
301                            Class<?> testClass = description.getTestClass();
302    
303                            _afterMethodKeys = getMethodKeys(testClass, After.class);
304                            _beforeMethodKeys = getMethodKeys(testClass, Before.class);
305                            _newClassLoader = createClassLoader(description);
306                            _testClassName = testClass.getName();
307                            _testMethodKey = new MethodKey(
308                                    testClass, description.getMethodName());
309                    }
310    
311                    @Override
312                    public void evaluate() throws Throwable {
313                            MethodCache.reset();
314    
315                            Thread currentThread = Thread.currentThread();
316    
317                            ClassLoader contextClassLoader =
318                                    currentThread.getContextClassLoader();
319    
320                            currentThread.setContextClassLoader(_newClassLoader);
321    
322                            String quiet = System.getProperty(
323                                    SystemProperties.SYSTEM_PROPERTIES_QUIET);
324    
325                            System.setProperty(
326                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
327    
328                            try {
329                                    Class<?> clazz = _newClassLoader.loadClass(_testClassName);
330    
331                                    Object object = clazz.newInstance();
332    
333                                    for (MethodKey beforeMethodKey : _beforeMethodKeys) {
334                                            invoke(_newClassLoader, beforeMethodKey, object);
335                                    }
336    
337                                    invoke(_newClassLoader, _testMethodKey, object);
338    
339                                    for (MethodKey afterMethodKey : _afterMethodKeys) {
340                                            invoke(_newClassLoader, afterMethodKey, object);
341                                    }
342                            }
343                            catch (InvocationTargetException ite) {
344                                    throw ite.getTargetException();
345                            }
346                            finally {
347                                    if (quiet == null) {
348                                            System.clearProperty(
349                                                    SystemProperties.SYSTEM_PROPERTIES_QUIET);
350                                    }
351                                    else {
352                                            System.setProperty(
353                                                    SystemProperties.SYSTEM_PROPERTIES_QUIET, quiet);
354                                    }
355    
356                                    currentThread.setContextClassLoader(contextClassLoader);
357    
358                                    MethodCache.reset();
359                            }
360                    }
361    
362                    private final List<MethodKey> _afterMethodKeys;
363                    private final List<MethodKey> _beforeMethodKeys;
364                    private final ClassLoader _newClassLoader;
365                    private final String _testClassName;
366                    private final MethodKey _testMethodKey;
367    
368            }
369    
370            private class RunInNewJVMStatment extends StatementWrapper {
371    
372                    public RunInNewJVMStatment(
373                            ProcessConfig processConfig, Statement statement,
374                            Description description) {
375    
376                            super(statement);
377    
378                            _processConfig = processConfig;
379    
380                            Class<?> testClass = description.getTestClass();
381    
382                            _afterMethodKeys = getMethodKeys(testClass, After.class);
383                            _beforeMethodKeys = getMethodKeys(testClass, Before.class);
384                            _testClassName = testClass.getName();
385                            _testMethodKey = new MethodKey(
386                                    testClass, description.getMethodName());
387                    }
388    
389                    @Override
390                    public void evaluate() throws Throwable {
391                            ProcessCallable<Serializable> processCallable =
392                                    new TestProcessCallable(
393                                            _testClassName, _beforeMethodKeys, _testMethodKey,
394                                            _afterMethodKeys);
395    
396                            processCallable = processProcessCallable(
397                                    processCallable, _testMethodKey);
398    
399                            ProcessChannel<Serializable> processChannel =
400                                    _processExecutor.execute(_processConfig, processCallable);
401    
402                            Future<Serializable> future =
403                                    processChannel.getProcessNoticeableFuture();
404    
405                            try {
406                                    future.get();
407                            }
408                            catch (ExecutionException ee) {
409                                    Throwable cause = ee.getCause();
410    
411                                    while ((cause instanceof ProcessException) ||
412                                               (cause instanceof InvocationTargetException)) {
413    
414                                            cause = cause.getCause();
415                                    }
416    
417                                    throw cause;
418                            }
419                    }
420    
421                    private final List<MethodKey> _afterMethodKeys;
422                    private final List<MethodKey> _beforeMethodKeys;
423                    private final ProcessConfig _processConfig;
424                    private final String _testClassName;
425                    private final MethodKey _testMethodKey;
426    
427            }
428    
429    }