/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.test.rule;
import com.liferay.portal.kernel.process.ClassPathUtil;
import com.liferay.portal.kernel.process.ProcessCallable;
import com.liferay.portal.kernel.process.ProcessChannel;
import com.liferay.portal.kernel.process.ProcessConfig;
import com.liferay.portal.kernel.process.ProcessConfig.Builder;
import com.liferay.portal.kernel.process.ProcessException;
import com.liferay.portal.kernel.process.ProcessExecutor;
import com.liferay.portal.kernel.process.local.LocalProcessExecutor;
import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ProcessContext;
import com.liferay.portal.kernel.process.local.LocalProcessLauncher.ShutdownHook;
import com.liferay.portal.kernel.test.rule.BaseTestRule.StatementWrapper;
import com.liferay.portal.kernel.test.rule.NewEnv.JVMArgsLine;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.MethodCache;
import com.liferay.portal.kernel.util.MethodKey;
import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.SystemProperties;
import com.liferay.portal.kernel.util.Validator;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.After;
import org.junit.Before;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
/**
* @author Shuyang Zhou
*/
public class NewEnvTestRule implements TestRule {
public static final NewEnvTestRule INSTANCE = new NewEnvTestRule();
@Override
public Statement apply(Statement statement, Description description) {
String methodName = description.getMethodName();
if (methodName == null) {
return statement;
}
NewEnv newEnv = findNewEnv(description);
if ((newEnv == null) || (newEnv.type() == NewEnv.Type.NONE)) {
return statement;
}
if (NewEnv.Type.CLASSLOADER == newEnv.type()) {
return new RunInNewClassLoaderStatement(statement, description);
}
Builder builder = new Builder();
builder.setArguments(createArguments(description));
builder.setBootstrapClassPath(CLASS_PATH);
builder.setRuntimeClassPath(CLASS_PATH);
return new RunInNewJVMStatment(builder.build(), statement, description);
}
protected static void attachProcess(String message) {
if (Boolean.getBoolean("attached")) {
return;
}
ProcessContext.attach(
message, 1000,
new ShutdownHook() {
@Override
public boolean shutdown(
int shutdownCode, Throwable shutdownThrowable) {
System.exit(shutdownCode);
return true;
}
});
System.setProperty("attached", StringPool.TRUE);
}
protected static List<MethodKey> getMethodKeys(
Class<?> targetClass, Class<? extends Annotation> annotationClass) {
TestClass testClass = new TestClass(targetClass);
List<FrameworkMethod> frameworkMethods = testClass.getAnnotatedMethods(
annotationClass);
List<MethodKey> methodKeys = new ArrayList<>(frameworkMethods.size());
for (FrameworkMethod annotatedFrameworkMethod : frameworkMethods) {
methodKeys.add(new MethodKey(annotatedFrameworkMethod.getMethod()));
}
return methodKeys;
}
protected static void invoke(
ClassLoader classLoader, MethodKey methodKey, Object object)
throws Exception {
methodKey = methodKey.transform(classLoader);
Method method = methodKey.getMethod();
method.invoke(object);
}
protected NewEnvTestRule() {
}
protected List<String> createArguments(Description description) {
List<String> arguments = new ArrayList<>();
Class<?> testClass = description.getTestClass();
JVMArgsLine jvmArgsLine = testClass.getAnnotation(JVMArgsLine.class);
if (jvmArgsLine != null) {
arguments.addAll(processJVMArgsLine(jvmArgsLine));
}
jvmArgsLine = description.getAnnotation(JVMArgsLine.class);
if (jvmArgsLine != null) {
arguments.addAll(processJVMArgsLine(jvmArgsLine));
}
arguments.add("-Djava.net.preferIPv4Stack=true");
if (Boolean.getBoolean("jvm.debug")) {
arguments.add(_JPDA_OPTIONS);
arguments.add("-Djvm.debug=true");
}
arguments.add("-Dliferay.mode=test");
arguments.add("-Dsun.zip.disableMemoryMapping=true");
String whipAgentLine = System.getProperty("whip.agent");
if (Validator.isNotNull(whipAgentLine)) {
arguments.add(whipAgentLine);
arguments.add("-Dwhip.agent=" + whipAgentLine);
}
String fileName = System.getProperty("whip.datafile");
if (fileName != null) {
arguments.add("-Dwhip.datafile=" + fileName);
}
if (Boolean.getBoolean("whip.instrument.dump")) {
arguments.add("-Dwhip.instrument.dump=true");
}
if (Boolean.getBoolean("whip.static.instrument")) {
arguments.add("-Dwhip.static.instrument=true");
}
return arguments;
}
protected ClassLoader createClassLoader(Description description) {
try {
return new URLClassLoader(
ClassPathUtil.getClassPathURLs(CLASS_PATH), null);
}
catch (MalformedURLException murle) {
throw new RuntimeException(murle);
}
}
protected NewEnv findNewEnv(Description description) {
NewEnv newEnv = description.getAnnotation(NewEnv.class);
if (newEnv == null) {
Class<?> testClass = description.getTestClass();
newEnv = testClass.getAnnotation(NewEnv.class);
}
return newEnv;
}
protected List<String> processJVMArgsLine(JVMArgsLine jvmArgsLine) {
String[] jvmArgs = StringUtil.split(
jvmArgsLine.value(), StringPool.SPACE);
List<String> jvmArgsList = new ArrayList<>(jvmArgs.length);
for (String jvmArg : jvmArgs) {
Matcher matcher = _systemPropertyReplacePattern.matcher(jvmArg);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String key = matcher.group(1);
matcher.appendReplacement(
sb, GetterUtil.getString(System.getProperty(key)));
}
matcher.appendTail(sb);
jvmArgsList.add(sb.toString());
}
return jvmArgsList;
}
protected ProcessCallable<Serializable> processProcessCallable(
ProcessCallable<Serializable> processCallable,
MethodKey testMethodKey) {
return processCallable;
}
protected static final String CLASS_PATH = ClassPathUtil.getJVMClassPath(
true);
private static final String _JPDA_OPTIONS =
"-agentlib:jdwp=transport=dt_socket,address=8001,server=y,suspend=y";
private static final ProcessExecutor _processExecutor =
new LocalProcessExecutor();
private static final Pattern _systemPropertyReplacePattern =
Pattern.compile("\\$\\{(.*)\\}");
static {
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader = currentThread.getContextClassLoader();
PortalClassLoaderUtil.setClassLoader(contextClassLoader);
}
private static class TestProcessCallable
implements ProcessCallable<Serializable> {
public TestProcessCallable(
String testClassName, List<MethodKey> beforeMethodKeys,
MethodKey testMethodKey, List<MethodKey> afterMethodKeys) {
_testClassName = testClassName;
_beforeMethodKeys = beforeMethodKeys;
_testMethodKey = testMethodKey;
_afterMethodKeys = afterMethodKeys;
}
@Override
public Serializable call() throws ProcessException {
attachProcess("Attached " + toString());
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader =
currentThread.getContextClassLoader();
System.setProperty(
SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
try {
Class<?> clazz = contextClassLoader.loadClass(_testClassName);
Object object = clazz.newInstance();
for (MethodKey beforeMethodKey : _beforeMethodKeys) {
invoke(contextClassLoader, beforeMethodKey, object);
}
invoke(contextClassLoader, _testMethodKey, object);
for (MethodKey afterMethodKey : _afterMethodKeys) {
invoke(contextClassLoader, afterMethodKey, object);
}
}
catch (Exception e) {
throw new ProcessException(e);
}
return StringPool.BLANK;
}
@Override
public String toString() {
StringBundler sb = new StringBundler(4);
sb.append(_testClassName);
sb.append(StringPool.PERIOD);
sb.append(_testMethodKey.getMethodName());
sb.append("()");
return sb.toString();
}
private static final long serialVersionUID = 1L;
private final List<MethodKey> _afterMethodKeys;
private final List<MethodKey> _beforeMethodKeys;
private final String _testClassName;
private final MethodKey _testMethodKey;
}
private class RunInNewClassLoaderStatement extends StatementWrapper {
public RunInNewClassLoaderStatement(
Statement statement, Description description) {
super(statement);
Class<?> testClass = description.getTestClass();
_afterMethodKeys = getMethodKeys(testClass, After.class);
_beforeMethodKeys = getMethodKeys(testClass, Before.class);
_newClassLoader = createClassLoader(description);
_testClassName = testClass.getName();
_testMethodKey = new MethodKey(
testClass, description.getMethodName());
}
@Override
public void evaluate() throws Throwable {
MethodCache.reset();
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader =
currentThread.getContextClassLoader();
currentThread.setContextClassLoader(_newClassLoader);
String quiet = System.getProperty(
SystemProperties.SYSTEM_PROPERTIES_QUIET);
System.setProperty(
SystemProperties.SYSTEM_PROPERTIES_QUIET, StringPool.TRUE);
try {
Class<?> clazz = _newClassLoader.loadClass(_testClassName);
Object object = clazz.newInstance();
for (MethodKey beforeMethodKey : _beforeMethodKeys) {
invoke(_newClassLoader, beforeMethodKey, object);
}
invoke(_newClassLoader, _testMethodKey, object);
for (MethodKey afterMethodKey : _afterMethodKeys) {
invoke(_newClassLoader, afterMethodKey, object);
}
}
catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
finally {
if (quiet == null) {
System.clearProperty(
SystemProperties.SYSTEM_PROPERTIES_QUIET);
}
else {
System.setProperty(
SystemProperties.SYSTEM_PROPERTIES_QUIET, quiet);
}
currentThread.setContextClassLoader(contextClassLoader);
MethodCache.reset();
}
}
private final List<MethodKey> _afterMethodKeys;
private final List<MethodKey> _beforeMethodKeys;
private final ClassLoader _newClassLoader;
private final String _testClassName;
private final MethodKey _testMethodKey;
}
private class RunInNewJVMStatment extends StatementWrapper {
public RunInNewJVMStatment(
ProcessConfig processConfig, Statement statement,
Description description) {
super(statement);
_processConfig = processConfig;
Class<?> testClass = description.getTestClass();
_afterMethodKeys = getMethodKeys(testClass, After.class);
_beforeMethodKeys = getMethodKeys(testClass, Before.class);
_testClassName = testClass.getName();
_testMethodKey = new MethodKey(
testClass, description.getMethodName());
}
@Override
public void evaluate() throws Throwable {
ProcessCallable<Serializable> processCallable =
new TestProcessCallable(
_testClassName, _beforeMethodKeys, _testMethodKey,
_afterMethodKeys);
processCallable = processProcessCallable(
processCallable, _testMethodKey);
ProcessChannel<Serializable> processChannel =
_processExecutor.execute(_processConfig, processCallable);
Future<Serializable> future =
processChannel.getProcessNoticeableFuture();
try {
future.get();
}
catch (ExecutionException ee) {
Throwable cause = ee.getCause();
while ((cause instanceof ProcessException) ||
(cause instanceof InvocationTargetException)) {
cause = cause.getCause();
}
throw cause;
}
}
private final List<MethodKey> _afterMethodKeys;
private final List<MethodKey> _beforeMethodKeys;
private final ProcessConfig _processConfig;
private final String _testClassName;
private final MethodKey _testMethodKey;
}
}