Skip to content

Commit 79d39c7

Browse files
Junit5 test with dependencies runner (#2962)
Added capability to run JUnit5 test with different dependencies Co-authored-by: Sylvain Juge <sylvain.juge@elastic.co>
1 parent 1961192 commit 79d39c7

File tree

25 files changed

+259
-124
lines changed

25 files changed

+259
-124
lines changed

apm-agent-core/src/test/java/co/elastic/apm/agent/TestClassWithDependencyRunner.java renamed to apm-agent-core/src/test/java/co/elastic/apm/agent/testutils/AbstractTestClassWithDependencyRunner.java

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
package co.elastic.apm.agent;
19+
package co.elastic.apm.agent.testutils;
2020

2121
import co.elastic.test.ChildFirstURLClassLoader;
2222
import org.apache.ivy.Ivy;
@@ -29,78 +29,40 @@
2929
import org.apache.ivy.core.settings.IvySettings;
3030
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
3131
import org.apache.ivy.plugins.resolver.URLResolver;
32-
import org.junit.runner.JUnitCore;
33-
import org.junit.runner.Result;
34-
import org.junit.runner.notification.Failure;
35-
import org.junit.runners.BlockJUnit4ClassRunner;
3632

37-
import javax.annotation.Nullable;
3833
import java.io.File;
3934
import java.io.FileOutputStream;
4035
import java.io.IOException;
4136
import java.io.InputStream;
42-
import java.lang.ref.WeakReference;
4337
import java.net.URL;
4438
import java.net.URLClassLoader;
4539
import java.util.ArrayList;
4640
import java.util.Arrays;
47-
import java.util.Collections;
4841
import java.util.List;
4942
import java.util.jar.JarEntry;
5043
import java.util.jar.JarOutputStream;
5144

5245
import static org.assertj.core.api.Assertions.assertThat;
5346

54-
public class TestClassWithDependencyRunner {
47+
public abstract class AbstractTestClassWithDependencyRunner {
5548

56-
private WeakReference<ClassLoader> classLoader;
57-
@Nullable
58-
private BlockJUnit4ClassRunner testRunner;
49+
protected final Class<?> testClass;
5950

6051
/**
6152
* Downloads the dependency and all its transitive dependencies via Apache Ivy.
6253
* Also exports a jar for the test class and all classes which reference the provided dependency.
6354
* All downloaded and exported jar files are then loaded from class loader with child-first semantics.
6455
* This avoids that the dependency will be loaded by the parent class loader which contains the {@code provided}-scoped maven dependency.
6556
*/
66-
public TestClassWithDependencyRunner(String groupId, String artifactId, String version, Class<?> testClass, Class<?>... classesReferencingDependency) throws Exception {
67-
this(Collections.singletonList(groupId + ":" + artifactId + ":" + version), testClass, classesReferencingDependency);
68-
}
69-
70-
public TestClassWithDependencyRunner(List<String> dependencies, Class<?> testClass, Class<?>... classesReferencingDependency) throws Exception {
71-
this(dependencies, testClass.getName(), Arrays.stream(classesReferencingDependency).map(Class::getName).toArray(String[]::new));
72-
}
73-
74-
public TestClassWithDependencyRunner(List<String> dependencies, String testClass, String... classesReferencingDependency) throws Exception {
57+
protected AbstractTestClassWithDependencyRunner(List<String> dependencies, String testClass, String... classesReferencingDependency) throws Exception {
7558
List<URL> urls = resolveArtifacts(dependencies);
7659
List<String> classesToExport = new ArrayList<>();
7760
classesToExport.add(testClass);
7861
classesToExport.addAll(Arrays.asList(classesReferencingDependency));
7962
urls.add(exportToTempJarFile(classesToExport));
8063

8164
URLClassLoader testClassLoader = new ChildFirstURLClassLoader(urls);
82-
testRunner = new BlockJUnit4ClassRunner(testClassLoader.loadClass(testClass));
83-
classLoader = new WeakReference<>(testClassLoader);
84-
}
85-
86-
public void run() {
87-
if (testRunner == null) {
88-
throw new IllegalStateException();
89-
}
90-
Result result = new JUnitCore().run(testRunner);
91-
for (Failure failure : result.getFailures()) {
92-
System.out.println(failure);
93-
failure.getException().printStackTrace();
94-
}
95-
assertThat(result.wasSuccessful()).isTrue();
96-
}
97-
98-
public void assertClassLoaderIsGCed() {
99-
testRunner = null;
100-
System.gc();
101-
System.gc();
102-
System.gc();
103-
assertThat(classLoader.get()).isNull();
65+
this.testClass = testClassLoader.loadClass(testClass);
10466
}
10567

10668
private static URL exportToTempJarFile(List<String> classes) throws IOException {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.testutils;
20+
21+
import org.junit.runner.JUnitCore;
22+
import org.junit.runner.Result;
23+
import org.junit.runner.notification.Failure;
24+
import org.junit.runners.BlockJUnit4ClassRunner;
25+
26+
import javax.annotation.Nullable;
27+
import java.util.Arrays;
28+
import java.util.Collections;
29+
import java.util.List;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* @deprecated use {@link TestClassWithDependencyRunner} and test with junit5 instead
35+
*/
36+
@Deprecated
37+
public class JUnit4TestClassWithDependencyRunner extends AbstractTestClassWithDependencyRunner {
38+
39+
@Nullable
40+
private final BlockJUnit4ClassRunner testRunner;
41+
42+
public JUnit4TestClassWithDependencyRunner(String groupId, String artifactId, String version, Class<?> testClass, Class<?>... classesReferencingDependency) throws Exception {
43+
this(Collections.singletonList(groupId + ":" + artifactId + ":" + version), testClass, classesReferencingDependency);
44+
}
45+
46+
public JUnit4TestClassWithDependencyRunner(List<String> dependencies, Class<?> testClass, Class<?>... classesReferencingDependency) throws Exception {
47+
this(dependencies, testClass.getName(), Arrays.stream(classesReferencingDependency).map(Class::getName).toArray(String[]::new));
48+
}
49+
50+
public JUnit4TestClassWithDependencyRunner(List<String> dependencies, String testClass, String... classesReferencingDependency) throws Exception {
51+
super(dependencies, testClass, classesReferencingDependency);
52+
testRunner = new BlockJUnit4ClassRunner(this.testClass);
53+
}
54+
55+
public void run() {
56+
if (testRunner == null) {
57+
throw new IllegalStateException();
58+
}
59+
Result result = new JUnitCore().run(testRunner);
60+
for (Failure failure : result.getFailures()) {
61+
System.out.println(failure);
62+
failure.getException().printStackTrace();
63+
}
64+
assertThat(result.wasSuccessful()).isTrue();
65+
}
66+
67+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.testutils;
20+
21+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
22+
import org.junit.jupiter.api.extension.ExecutionCondition;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.junit.jupiter.api.extension.ExtensionContext;
25+
import org.junit.platform.launcher.Launcher;
26+
import org.junit.platform.launcher.LauncherDiscoveryRequest;
27+
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
28+
import org.junit.platform.launcher.core.LauncherFactory;
29+
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
30+
import org.junit.platform.launcher.listeners.TestExecutionSummary;
31+
32+
import java.lang.annotation.Documented;
33+
import java.lang.annotation.ElementType;
34+
import java.lang.annotation.Retention;
35+
import java.lang.annotation.RetentionPolicy;
36+
import java.lang.annotation.Target;
37+
import java.lang.reflect.AnnotatedElement;
38+
import java.util.List;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled;
42+
import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled;
43+
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
44+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
45+
46+
public class TestClassWithDependencyRunner extends AbstractTestClassWithDependencyRunner {
47+
48+
49+
/**
50+
* Prevents test from running when the test class is not executed from within a {@link TestClassWithDependencyRunner}.
51+
* <p>
52+
* This if for example useful when attempting to test that an instrumentation is *not* active for unsupported versions
53+
* of the instrumentation target. To test this, one would create a test that checks that the instrumentation is not active
54+
* and run it via the {@link TestClassWithDependencyRunner} for the unsupported versions of the instrumentation target.
55+
* <p>
56+
* However, the test itself would also be run by maven outside the {@link TestClassWithDependencyRunner}, because it is a
57+
* normal unit test. In this environment the test is executed with the latest version of the instrumentation target (from the pom.xml),
58+
* which in turn would cause the test to fail because this version is actually supported.
59+
* To prevent these "wrong" failures, this annotation can be used to disable the test outside the {@link TestClassWithDependencyRunner}.
60+
*/
61+
@Target({ElementType.TYPE, ElementType.METHOD})
62+
@Retention(RetentionPolicy.RUNTIME)
63+
@Documented
64+
@ExtendWith(DisableOutsideOfRunnerCondition.class)
65+
public @interface DisableOutsideOfRunner {
66+
/**
67+
* @return a custom reason for disabling this outside of the dependency runner
68+
*/
69+
String value() default "";
70+
}
71+
72+
public static class DisableOutsideOfRunnerCondition implements ExecutionCondition {
73+
74+
@Override
75+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
76+
AnnotatedElement element = context.getElement().orElse(null);
77+
return findAnnotation(element, DisableOutsideOfRunner.class)
78+
.map(annotation -> disabled(element + " is @DisableOutsideDependencyRunner", annotation.value()))
79+
.orElse(enabled("@DisableOutsideDependencyRunner is not present"));
80+
}
81+
}
82+
83+
public TestClassWithDependencyRunner(List<String> dependencies, String testClass, String... classesReferencingDependency) throws Exception {
84+
super(dependencies, testClass, classesReferencingDependency);
85+
}
86+
87+
public void run() {
88+
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
89+
.selectors(selectClass(testClass))
90+
.configurationParameter("junit.jupiter.conditions.deactivate", DisableOutsideOfRunnerCondition.class.getName())
91+
.build();
92+
Launcher launcher = LauncherFactory.create();
93+
SummaryGeneratingListener listener = new SummaryGeneratingListener();
94+
launcher.registerTestExecutionListeners(listener);
95+
launcher.execute(request);
96+
97+
for (TestExecutionSummary.Failure failure : listener.getSummary().getFailures()) {
98+
System.out.println(failure);
99+
failure.getException().printStackTrace();
100+
}
101+
assertThat(listener.getSummary().getTestsFailedCount()).isZero();
102+
}
103+
104+
}

apm-agent-core/src/test/java/co/elastic/test/ChildFirstURLClassLoader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package co.elastic.test;
2020

2121
import co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers;
22+
import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner;
2223

2324
import java.io.IOException;
2425
import java.net.URL;
@@ -30,7 +31,7 @@
3031

3132
/**
3233
* A Child-first class loader used for tests.
33-
* Specifically, used within {@link co.elastic.apm.agent.TestClassWithDependencyRunner} for tests that require encapsulated
34+
* Specifically, used within {@link JUnit4TestClassWithDependencyRunner} for tests that require encapsulated
3435
* test classpath, for example - for testing specific library versions.
3536
* In order for classes that are loaded by this class loader to be instrumented, it must be outside of the {@code co.elastic.apm}
3637
* package, otherwise it may be excluded if tested through {@link CustomElementMatchers#isAgentClassLoader()}.

apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient3-plugin/src/test/java/co/elastic/apm/agent/httpclient/v3/HttpClient3VersionIT.java

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,22 @@
1818
*/
1919
package co.elastic.apm.agent.httpclient.v3;
2020

21-
import co.elastic.apm.agent.TestClassWithDependencyRunner;
22-
import org.junit.Test;
23-
import org.junit.runner.RunWith;
24-
import org.junit.runners.Parameterized;
21+
import co.elastic.apm.agent.testutils.TestClassWithDependencyRunner;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
2524

26-
import java.util.Arrays;
2725
import java.util.List;
2826

29-
@RunWith(Parameterized.class)
3027
public class HttpClient3VersionIT {
3128

32-
private final TestClassWithDependencyRunner runner;
33-
34-
public HttpClient3VersionIT(String dependency) throws Exception {
35-
this.runner = new TestClassWithDependencyRunner(List.of(dependency), HttpClient3InstrumentationTest.class);
36-
}
37-
38-
@Parameterized.Parameters(name = "{0}")
39-
public static Iterable<Object[]> data() {
40-
return Arrays.asList(new Object[][]{
41-
{"commons-httpclient:commons-httpclient:3.0"},
42-
{"commons-httpclient:commons-httpclient:3.1"}
43-
});
44-
}
45-
46-
@Test
47-
public void testVersion() {
29+
@ParameterizedTest
30+
@ValueSource(strings = {
31+
"3.0",
32+
"3.1"
33+
})
34+
void testVersion(String version) throws Exception {
35+
String dependency = String.format("commons-httpclient:commons-httpclient:" + version);
36+
TestClassWithDependencyRunner runner = new TestClassWithDependencyRunner(List.of(dependency), "co.elastic.apm.agent.httpclient.v3.HttpClient3InstrumentationTest");
4837
runner.run();
4938
}
5039
}

apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/test/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientVersionIT.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
package co.elastic.apm.agent.httpclient.v4;
2020

21-
import co.elastic.apm.agent.TestClassWithDependencyRunner;
21+
import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner;
2222
import org.junit.Test;
2323
import org.junit.runner.RunWith;
2424
import org.junit.runners.Parameterized;
@@ -29,12 +29,12 @@
2929
@RunWith(Parameterized.class)
3030
public class LegacyApacheHttpClientVersionIT {
3131

32-
private final TestClassWithDependencyRunner runner1;
33-
private final TestClassWithDependencyRunner runner2;
32+
private final JUnit4TestClassWithDependencyRunner runner1;
33+
private final JUnit4TestClassWithDependencyRunner runner2;
3434

3535
public LegacyApacheHttpClientVersionIT(List<String> dependencies) throws Exception {
36-
this.runner1 = new TestClassWithDependencyRunner(dependencies, LegacyApacheHttpClientBasicHttpRequestInstrumentationTest.class);
37-
this.runner2 = new TestClassWithDependencyRunner(dependencies, LegacyApacheHttpClientHttpUriRequestInstrumentationTest.class);
36+
this.runner1 = new JUnit4TestClassWithDependencyRunner(dependencies, LegacyApacheHttpClientBasicHttpRequestInstrumentationTest.class);
37+
this.runner2 = new JUnit4TestClassWithDependencyRunner(dependencies, LegacyApacheHttpClientHttpUriRequestInstrumentationTest.class);
3838
}
3939

4040
@Parameterized.Parameters(name = "{0}")

apm-agent-plugins/apm-ecs-logging-plugin/src/test/java/co/elastic/apm/agent/ecs_logging/Log4j2_17_1ServiceNameInstrumentationTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@
1818
*/
1919
package co.elastic.apm.agent.ecs_logging;
2020

21-
import co.elastic.apm.agent.TestClassWithDependencyRunner;
21+
import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner;
2222
import org.junit.jupiter.api.Test;
2323

2424
import java.util.List;
2525

2626
public class Log4j2_17_1ServiceNameInstrumentationTest {
27-
private final TestClassWithDependencyRunner runner;
27+
private final JUnit4TestClassWithDependencyRunner runner;
2828

2929
public Log4j2_17_1ServiceNameInstrumentationTest() throws Exception {
3030
List<String> dependencies = List.of("co.elastic.logging:log4j2-ecs-layout:1.3.2");
31-
runner = new TestClassWithDependencyRunner(dependencies, Log4j2ServiceNameInstrumentationTest.class);
31+
runner = new JUnit4TestClassWithDependencyRunner(dependencies, Log4j2ServiceNameInstrumentationTest.class);
3232
}
3333

3434
@Test

apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaClientVersionsIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
package co.elastic.apm.agent.kafka;
2020

21-
import co.elastic.apm.agent.TestClassWithDependencyRunner;
21+
import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner;
2222
import org.junit.Test;
2323
import org.junit.runner.RunWith;
2424
import org.junit.runners.Parameterized;
@@ -27,10 +27,10 @@
2727

2828
@RunWith(Parameterized.class)
2929
public class KafkaClientVersionsIT {
30-
private final TestClassWithDependencyRunner runner;
30+
private final JUnit4TestClassWithDependencyRunner runner;
3131

3232
public KafkaClientVersionsIT(String version) throws Exception {
33-
runner = new TestClassWithDependencyRunner("org.apache.kafka", "kafka-clients", version,
33+
runner = new JUnit4TestClassWithDependencyRunner("org.apache.kafka", "kafka-clients", version,
3434
KafkaIT.class, KafkaIT.Consumer.class, KafkaIT.RecordIterationMode.class, KafkaIT.TestScenario.class,
3535
KafkaIT.ConsumerRecordConsumer.class);
3636
}

0 commit comments

Comments
 (0)