001
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
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 }