From 182fc7d862a3aba2656d5fbc1cdea5fab85a9423 Mon Sep 17 00:00:00 2001 From: emeroad Date: Thu, 23 Jun 2022 18:29:16 +0900 Subject: [PATCH] [#8965] Add SharedTestLifeCycle to simplify plugin integration tests --- .../plugin/shared/ExecuteSharedThread.java | 37 +++++++++++-- .../shared/SharedPinpointPluginTest.java | 24 ++++++--- .../shared/SharedTestBeforeAllInvoker.java | 47 +++++++++++++++++ .../shared/SharedTestBeforeAllResult.java | 30 +++++++++++ .../plugin/shared/SharedTestLifeCycle.java | 11 ++++ .../shared/SharedTestLifeCycleClass.java | 31 +++++++++++ .../shared/SharedTestLifeCycleWrapper.java | 48 +++++++++++++++++ .../SharedTestBeforeAllInvokerTest.java | 52 +++++++++++++++++++ 8 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvoker.java create mode 100644 test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllResult.java create mode 100644 test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycle.java create mode 100644 test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleClass.java create mode 100644 test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleWrapper.java create mode 100644 test/src/test/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvokerTest.java diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/ExecuteSharedThread.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/ExecuteSharedThread.java index b16e2377402d..cc74fac6f8e8 100644 --- a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/ExecuteSharedThread.java +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/ExecuteSharedThread.java @@ -37,7 +37,7 @@ public class ExecuteSharedThread { private static final TaggedLogger logger = TestLogger.getLogger(); private final Thread thread; - + private final ExecuteSharedThreadRunnable runnable; private final String testClazzName; private final ClassLoader testClassLoader; @@ -50,7 +50,9 @@ public ExecuteSharedThread(String testClazzName, ClassLoader testClassLoader) { this.testClazzName = Objects.requireNonNull(testClazzName, "testClazzName"); this.testClassLoader = Objects.requireNonNull(testClassLoader, "testClassLoader"); - thread = new Thread(new ExecuteSharedThreadRunnable()); + this.runnable = new ExecuteSharedThreadRunnable(); + + thread = new Thread(runnable); thread.setName(testClazzName + "-Shared-Thread"); thread.setContextClassLoader(testClassLoader); thread.setDaemon(true); @@ -60,6 +62,14 @@ void startBefore() { thread.start(); } + public SharedTestLifeCycleWrapper getSharedClassWrapper() { + return runnable.sharedTestLifeCycleWrapper; + } + + public Throwable getRunnableError() { + return runnable.throwable; + } + boolean awaitBeforeCompleted(long timeout, TimeUnit unit) { if (!thread.isAlive()) { throw new IllegalStateException("Thread is not alive."); @@ -123,26 +133,43 @@ boolean join(long millis) { } private class ExecuteSharedThreadRunnable implements Runnable { - + private volatile SharedTestLifeCycleWrapper sharedTestLifeCycleWrapper; + private volatile Throwable throwable; @Override public void run() { - final Class testClazz; + Class testClazz = null; try { testClazz = loadClass(); logger.debug("Execute testClazz:{} cl:{}", testClazz.getName(), testClazz.getClassLoader()); + sharedTestLifeCycleWrapper = SharedTestLifeCycleWrapper.newVersionTestLifeCycleWrapper(testClazz); + if (sharedTestLifeCycleWrapper != null) { + sharedTestLifeCycleWrapper.beforeAll(); + } + runBeforeSharedClass(testClazz); Map result = getProperties(testClazz); if (result.size() > 0) { - properties.putAll(result); + // bind parameter + for (Map.Entry entry : result.entrySet()) { + if (entry.getValue() != null) { + properties.put(entry.getKey(), entry.getValue()); + } + } } + } catch (Throwable th) { + logger.warn("{} testclass error", testClazzName, th); + throwable = th; } finally { setBeforeCompleted(); } awaitAfterStart(); runAfterSharedClass(testClazz); + if (sharedTestLifeCycleWrapper != null) { + sharedTestLifeCycleWrapper.afterAll(); + } } private Class loadClass() { diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedPinpointPluginTest.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedPinpointPluginTest.java index 89f28fd38890..743324cf0dd4 100644 --- a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedPinpointPluginTest.java +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedPinpointPluginTest.java @@ -50,7 +50,7 @@ public class SharedPinpointPluginTest { private static final TaggedLogger logger = TestLogger.getLogger(); - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws Throwable { final String mavenDependencyResolverClassPaths = System.getProperty(SharedPluginTestConstants.MAVEN_DEPENDENCY_RESOLVER_CLASS_PATHS); if (mavenDependencyResolverClassPaths == null) { logger.error("mavenDependencyResolverClassPaths must not be empty"); @@ -202,7 +202,7 @@ private void logTestInformation() { } } - public void execute() throws Exception { + public void execute() throws Throwable { logTestInformation(); ClassLoader mavenDependencyResolverClassLoader = new ChildFirstClassLoader(URLUtils.fileToUrls(mavenDependencyResolverClassPaths)); File testClazzLocation = new File(testLocation); @@ -211,7 +211,7 @@ public void execute() throws Exception { executes(testInfos); } - private void executes(List testInfos) { + private void executes(List testInfos) throws Throwable { if (!CollectionUtils.hasLength(testInfos)) { return; } @@ -222,13 +222,18 @@ private void executes(List testInfos) { executeSharedThread.startBefore(); executeSharedThread.awaitBeforeCompleted(10, TimeUnit.MINUTES); + Throwable runnableError = executeSharedThread.getRunnableError(); + if (runnableError != null) { + throw runnableError; + } Properties properties = executeSharedThread.getProperties(); if (logger.isDebugEnabled()) { logger.debug("sharedThread properties:{}", properties); } + final SharedTestLifeCycleWrapper sharedTestLifeCycleWrapper = executeSharedThread.getSharedClassWrapper(); for (TestInfo testInfo : testInfos) { - execute(testInfo, properties); + execute(testInfo, properties, sharedTestLifeCycleWrapper); } executeSharedThread.startAfter(); @@ -248,7 +253,7 @@ private ClassLoader createTestClassLoader(TestInfo testInfo) { return testClassLoader; } - private void execute(final TestInfo testInfo, final Properties properties) { + private void execute(final TestInfo testInfo, final Properties properties, SharedTestLifeCycleWrapper sharedTestLifeCycleWrapper) { try { final ClassLoader testClassLoader = createTestClassLoader(testInfo); @@ -257,11 +262,16 @@ private void execute(final TestInfo testInfo, final Properties properties) { public void run() { final Class testClazz = loadClass(); logger.debug("testClazz:{} cl:{}", testClazz.getName(), testClazz.getClassLoader()); - + SharedTestBeforeAllInvoker invoker = new SharedTestBeforeAllInvoker(testClazz); + try { + invoker.invoke(sharedTestLifeCycleWrapper.getLifeCycleResult()); + } catch (Throwable th) { + logger.error(th, "invoker setter method failed. testClazz:{} testId:{}", testClazzName, testInfo.getTestId()); + } try { MethodUtils.invokeSetMethod(testClazz, properties); } catch (Exception e) { - logger.warn(e, "invoker setter method failed. testClazz:{} testId:{}", testClazzName, testInfo.getTestId()); + logger.error(e, "invoker setter method failed. testClazz:{} testId:{}", testClazzName, testInfo.getTestId()); } try { diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvoker.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvoker.java new file mode 100644 index 000000000000..b61ac0b3aaca --- /dev/null +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvoker.java @@ -0,0 +1,47 @@ +package com.navercorp.pinpoint.test.plugin.shared; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author emeroad + */ +public class SharedTestBeforeAllInvoker { + private final Class testClazz; + + public SharedTestBeforeAllInvoker(Class testClazz) { + this.testClazz = Objects.requireNonNull(testClazz, "testClazz"); + } + + + List getMethods(Class testClazz, Predicate predicate) { + Method[] methods = testClazz.getMethods(); + Stream stream = Arrays.stream(methods); + return stream.filter(predicate) + .collect(Collectors.toList()); + } + + boolean beforeAllFilter(Method method) { + if (method.getAnnotation(SharedTestBeforeAllResult.class) == null) { + return false; + } + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1) { + return false; + } + return Properties.class.isAssignableFrom(parameterTypes[0]); + } + + public void invoke(Properties properties) throws Throwable { + List methods = getMethods(testClazz, this::beforeAllFilter); + for (Method method : methods) { + method.invoke(testClazz, properties); + } + } +} diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllResult.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllResult.java new file mode 100644 index 000000000000..607e281c6eb5 --- /dev/null +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllResult.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.test.plugin.shared; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author emeroad + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SharedTestBeforeAllResult { +} \ No newline at end of file diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycle.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycle.java new file mode 100644 index 000000000000..c915bb06021b --- /dev/null +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycle.java @@ -0,0 +1,11 @@ +package com.navercorp.pinpoint.test.plugin.shared; + +import java.util.Properties; + +/** + * @author emeroad + */ +public interface SharedTestLifeCycle { + Properties beforeAll(); + void afterAll(); +} diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleClass.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleClass.java new file mode 100644 index 000000000000..0c9c48b87c15 --- /dev/null +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleClass.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.test.plugin.shared; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author emeroad + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SharedTestLifeCycleClass { + Class value(); +} \ No newline at end of file diff --git a/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleWrapper.java b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleWrapper.java new file mode 100644 index 000000000000..5688ca54cd70 --- /dev/null +++ b/test/src/main/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestLifeCycleWrapper.java @@ -0,0 +1,48 @@ +package com.navercorp.pinpoint.test.plugin.shared; + +import java.util.Objects; +import java.util.Properties; + +/** + * @author emeroad + */ +public class SharedTestLifeCycleWrapper { + private final SharedTestLifeCycle sharedTestLifecycle; + private Properties lifeCycleResult; + + private static Class getVersionTestLifeCycle(final Class testClazz) { + SharedTestLifeCycleClass sharedTestLifeCycleClass = testClazz.getAnnotation(SharedTestLifeCycleClass.class); + if (sharedTestLifeCycleClass == null) { + return null; + } + return sharedTestLifeCycleClass.value(); + } + + public static SharedTestLifeCycleWrapper newVersionTestLifeCycleWrapper(final Class testClazz) { + Class versionTestClazz = getVersionTestLifeCycle(testClazz); + if (versionTestClazz == null) { + return null; + } + try { + return new SharedTestLifeCycleWrapper(versionTestClazz.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public SharedTestLifeCycleWrapper(SharedTestLifeCycle sharedTestLifecycle) { + this.sharedTestLifecycle = Objects.requireNonNull(sharedTestLifecycle, "versionTestLifecycle"); + } + + public Properties getLifeCycleResult() { + return lifeCycleResult; + } + + public void beforeAll() { + lifeCycleResult = sharedTestLifecycle.beforeAll(); + } + + public void afterAll() { + sharedTestLifecycle.afterAll(); + } +} diff --git a/test/src/test/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvokerTest.java b/test/src/test/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvokerTest.java new file mode 100644 index 000000000000..e79b8116799f --- /dev/null +++ b/test/src/test/java/com/navercorp/pinpoint/test/plugin/shared/SharedTestBeforeAllInvokerTest.java @@ -0,0 +1,52 @@ +package com.navercorp.pinpoint.test.plugin.shared; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Properties; + +public class SharedTestBeforeAllInvokerTest { + + @Test + public void invoke() throws Throwable { + SharedTestBeforeAllInvoker mock = new SharedTestBeforeAllInvoker(TestClass.class); + + List methods = mock.getMethods(TestClass.class, mock::beforeAllFilter); + Assert.assertEquals(1, methods.size()); + Assert.assertEquals("setBeforeAllResult", methods.get(0).getName()); + + mock.invoke(new Properties()); + Assert.assertNotNull(TestClass.properties); + } + + @Test + public void invoke_extends() throws Throwable { + SharedTestBeforeAllInvoker mock = new SharedTestBeforeAllInvoker(ChildTestClass.class); + + List methods = mock.getMethods(ChildTestClass.class, mock::beforeAllFilter); + Assert.assertEquals(1, methods.size()); + Assert.assertEquals("setBeforeAllResult", methods.get(0).getName()); + + mock.invoke(new Properties()); + Assert.assertNotNull(ChildTestClass.properties); + } + + public static class TestClass { + static Properties properties; + + @SharedTestBeforeAllResult + public static void setBeforeAllResult(Properties properties) { + TestClass.properties = properties; + } + + public static void fake(Properties p) { + + } + } + + public static class ChildTestClass extends TestClass { + + } +} \ No newline at end of file