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