Skip to content

Commit

Permalink
4.x: Ability to use HelidonTest in a meta-annotation #4918
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Bescos Gascon <[email protected]>
  • Loading branch information
jbescos committed May 14, 2024
1 parent 74d06e7 commit 2db13aa
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import io.helidon.config.mp.MpConfigSources;
import io.helidon.microprofile.server.JaxRsCdiExtension;
Expand Down Expand Up @@ -88,7 +90,8 @@ class HelidonJunitExtension implements BeforeAllCallback,
InvocationInterceptor,
ParameterResolver {
private static final Set<Class<? extends Annotation>> HELIDON_TEST_ANNOTATIONS =
Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class, AddJaxRs.class);
Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class,
AddJaxRs.class, AddConfigBlock.class);
private static final Map<Class<? extends Annotation>, Annotation> BEAN_DEFINING = new HashMap<>();

static {
Expand All @@ -115,24 +118,26 @@ class HelidonJunitExtension implements BeforeAllCallback,
public void beforeAll(ExtensionContext context) {
testClass = context.getRequiredTestClass();

AddConfig[] configs = getAnnotations(testClass, AddConfig.class);
List<Annotation> metaAnnotations = extractMetaAnnotations(testClass);

AddConfig[] configs = getAnnotations(testClass, AddConfig.class, metaAnnotations);
classLevelConfigMeta.addConfig(configs);
classLevelConfigMeta.configuration(testClass.getAnnotation(Configuration.class));
classLevelConfigMeta.addConfigBlock(testClass.getAnnotation(AddConfigBlock.class));
classLevelConfigMeta.configuration(getAnnotation(testClass, Configuration.class, metaAnnotations));
classLevelConfigMeta.addConfigBlock(getAnnotation(testClass, AddConfigBlock.class, metaAnnotations));
configProviderResolver = ConfigProviderResolver.instance();

AddExtension[] extensions = getAnnotations(testClass, AddExtension.class);
AddExtension[] extensions = getAnnotations(testClass, AddExtension.class, metaAnnotations);
classLevelExtensions.addAll(Arrays.asList(extensions));

AddBean[] beans = getAnnotations(testClass, AddBean.class);
AddBean[] beans = getAnnotations(testClass, AddBean.class, metaAnnotations);
classLevelBeans.addAll(Arrays.asList(beans));

HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class);
if (testAnnot != null) {
resetPerTest = testAnnot.resetPerTest();
}

DisableDiscovery discovery = testClass.getAnnotation(DisableDiscovery.class);
DisableDiscovery discovery = getAnnotation(testClass, DisableDiscovery.class, metaAnnotations);
if (discovery != null) {
classLevelDisableDiscovery = discovery.value();
}
Expand All @@ -145,7 +150,7 @@ public void beforeAll(ExtensionContext context) {
validatePerClass();

// add beans when using JaxRS
AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class);
AddJaxRs addJaxRsAnnotation = getAnnotation(testClass, AddJaxRs.class, metaAnnotations);
if (addJaxRsAnnotation != null){
classLevelExtensions.add(ProcessAllAnnotatedTypesLiteral.INSTANCE);
classLevelExtensions.add(ServerCdiExtensionLiteral.INSTANCE);
Expand All @@ -164,13 +169,42 @@ public void beforeAll(ExtensionContext context) {
}
}

private List<Annotation> extractMetaAnnotations(Class<?> testClass) {
Annotation[] testAnnotations = testClass.getAnnotations();
for (Annotation testAnnotation : testAnnotations) {
List<Annotation> annotations = List.of(testAnnotation.annotationType().getAnnotations());
List<Class<?>> annotationsClass = annotations.stream()
.map(a -> a.annotationType()).collect(Collectors.toList());
if (!Collections.disjoint(HELIDON_TEST_ANNOTATIONS, annotationsClass)) {
// Contains at least one of HELIDON_TEST_ANNOTATIONS
return annotations;
}
}
return List.of();
}

private <T extends Annotation> T getAnnotation(Class<?> testClass, Class<T> annotClass,
List<Annotation> metaAnnotations) {
T annotation = testClass.getAnnotation(annotClass);
if (annotation == null) {
List<T> byType = annotationsByType(annotClass, metaAnnotations);
if (!byType.isEmpty()) {
annotation = byType.get(0);
}
}
return annotation;
}

@SuppressWarnings("unchecked")
private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> annotClass) {
private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> annotClass,
List<Annotation> metaAnnotations) {
// inherited does not help, as it only returns annot from superclass if
// child has none
T[] directAnnotations = testClass.getAnnotationsByType(annotClass);

List<T> allAnnotations = new ArrayList<>(List.of(directAnnotations));
// Include meta annotations
allAnnotations.addAll(annotationsByType(annotClass, metaAnnotations));

Class<?> superClass = testClass.getSuperclass();
while (superClass != null) {
Expand All @@ -187,6 +221,16 @@ private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> a
return (T[]) result;
}

private <T extends Annotation> List<T> annotationsByType(Class<T> annotClass, List<Annotation> metaAnnotations) {
List<T> byType = new ArrayList<>();
for (Annotation annotation : metaAnnotations) {
if (annotation.annotationType() == annotClass) {
byType.add((T) annotation);
}
}
return byType;
}

@Override
public void beforeEach(ExtensionContext context) throws Exception {
if (resetPerTest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import io.helidon.config.mp.MpConfigSources;
import io.helidon.microprofile.server.JaxRsCdiExtension;
Expand Down Expand Up @@ -77,7 +79,8 @@
public class HelidonTestNgListener implements IClassListener, ITestListener {

private static final Set<Class<? extends Annotation>> HELIDON_TEST_ANNOTATIONS =
Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class, AddJaxRs.class);
Set.of(AddBean.class, AddConfig.class, AddExtension.class,
Configuration.class, AddJaxRs.class, AddConfigBlock.class);
private static final Map<Class<? extends Annotation>, Annotation> BEAN_DEFINING = new HashMap<>();

static {
Expand All @@ -103,24 +106,26 @@ public void onBeforeClass(ITestClass iTestClass) {

testClass = iTestClass.getRealClass();

AddConfig[] configs = getAnnotations(testClass, AddConfig.class);
List<Annotation> metaAnnotations = extractMetaAnnotations(testClass);

AddConfig[] configs = getAnnotations(testClass, AddConfig.class, metaAnnotations);
classLevelConfigMeta.addConfig(configs);
classLevelConfigMeta.configuration(testClass.getAnnotation(Configuration.class));
classLevelConfigMeta.addConfigBlock(testClass.getAnnotation(AddConfigBlock.class));
classLevelConfigMeta.configuration(getAnnotation(testClass, Configuration.class, metaAnnotations));
classLevelConfigMeta.addConfigBlock(getAnnotation(testClass, AddConfigBlock.class, metaAnnotations));
configProviderResolver = ConfigProviderResolver.instance();

AddExtension[] extensions = getAnnotations(testClass, AddExtension.class);
AddExtension[] extensions = getAnnotations(testClass, AddExtension.class, metaAnnotations);
classLevelExtensions.addAll(Arrays.asList(extensions));

AddBean[] beans = getAnnotations(testClass, AddBean.class);
AddBean[] beans = getAnnotations(testClass, AddBean.class, metaAnnotations);
classLevelBeans.addAll(Arrays.asList(beans));

HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class);
if (testAnnot != null) {
resetPerTest = testAnnot.resetPerTest();
}

DisableDiscovery discovery = testClass.getAnnotation(DisableDiscovery.class);
DisableDiscovery discovery = getAnnotation(testClass, DisableDiscovery.class, metaAnnotations);
if (discovery != null) {
classLevelDisableDiscovery = discovery.value();
}
Expand All @@ -132,7 +137,7 @@ public void onBeforeClass(ITestClass iTestClass) {
validatePerClass();

// add beans when using JaxRS
AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class);
AddJaxRs addJaxRsAnnotation = getAnnotation(testClass, AddJaxRs.class, metaAnnotations);
if (addJaxRsAnnotation != null){
classLevelExtensions.add(ProcessAllAnnotatedTypesLiteral.INSTANCE);
classLevelExtensions.add(ServerCdiExtensionLiteral.INSTANCE);
Expand Down Expand Up @@ -398,14 +403,52 @@ private void stopContainer() {
}
}

private List<Annotation> extractMetaAnnotations(Class<?> testClass) {
Annotation[] testAnnotations = testClass.getAnnotations();
for (Annotation testAnnotation : testAnnotations) {
List<Annotation> annotations = List.of(testAnnotation.annotationType().getAnnotations());
List<Class<?>> annotationsClass = annotations.stream()
.map(a -> a.annotationType()).collect(Collectors.toList());
if (!Collections.disjoint(HELIDON_TEST_ANNOTATIONS, annotationsClass)) {
// Contains at least one of HELIDON_TEST_ANNOTATIONS
return annotations;
}
}
return List.of();
}

private <T extends Annotation> List<T> annotationsByType(Class<T> annotClass, List<Annotation> metaAnnotations) {
List<T> byType = new ArrayList<>();
for (Annotation annotation : metaAnnotations) {
if (annotation.annotationType() == annotClass) {
byType.add((T) annotation);
}
}
return byType;
}

private <T extends Annotation> T getAnnotation(Class<?> testClass, Class<T> annotClass,
List<Annotation> metaAnnotations) {
T annotation = testClass.getAnnotation(annotClass);
if (annotation == null) {
List<T> byType = annotationsByType(annotClass, metaAnnotations);
if (!byType.isEmpty()) {
annotation = byType.get(0);
}
}
return annotation;
}

@SuppressWarnings("unchecked")
private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> annotClass) {
private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> annotClass,
List<Annotation> metaAnnotations) {
// inherited does not help, as it only returns annot from superclass if
// child has none
T[] directAnnotations = testClass.getAnnotationsByType(annotClass);

List<T> allAnnotations = new ArrayList<>(List.of(directAnnotations));
// Include meta annotations
allAnnotations.addAll(annotationsByType(annotClass, metaAnnotations));

Class<?> superClass = testClass.getSuperclass();
while (superClass != null) {
Expand All @@ -416,7 +459,7 @@ private <T extends Annotation> T[] getAnnotations(Class<?> testClass, Class<T> a

Object result = Array.newInstance(annotClass, allAnnotations.size());
for (int i = 0; i < allAnnotations.size(); i++) {
Array.set(result, i, allAnnotations.get(i));
Array.set(result, i, allAnnotations.get(i));
}

return (T[]) result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.microprofile.tests.testing.junit5;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import jakarta.inject.Inject;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import io.helidon.microprofile.testing.junit5.AddBean;
import io.helidon.microprofile.testing.junit5.AddConfig;
import io.helidon.microprofile.testing.junit5.AddConfigBlock;
import io.helidon.microprofile.testing.junit5.Configuration;
import io.helidon.microprofile.testing.junit5.HelidonTest;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.junit.jupiter.api.Test;

@TestMetaAnnotation.MetaAnnotation
// @HelidonTest is still mandatory in the test and it has no effect in the MetaAnnotation
@HelidonTest
class TestMetaAnnotation {

@Inject
MyBean bean;

@Inject
@ConfigProperty(name = "some.key1")
private String value1;

@Inject
@ConfigProperty(name = "some.key2")
private String value2;

@Inject
@ConfigProperty(name = "some.key")
private String someKey;

@Inject
@ConfigProperty(name = "another.key")
private String anotherKey;

@Inject
@ConfigProperty(name = "second-key")
private String anotherValue;

@Test
void testIt() {
assertThat(bean.hello(), is("hello"));
assertThat(value1, is("some.value1"));
assertThat(value2, is("some.value2"));
assertThat(someKey, is("some.value"));
assertThat(anotherKey, is("another.value"));
assertThat(anotherValue, is("test-custom-config-second-value"));
}

@AddBean(MyBean.class)
@AddConfigBlock("""
some.key1=some.value1
some.key2=some.value2
""")
@AddConfig(key = "second-key", value = "test-custom-config-second-value")
@Configuration(configSources = {"testConfigSources.properties", "testConfigSources.yaml"})
@Retention(RetentionPolicy.RUNTIME)
static @interface MetaAnnotation {
}

static class MyBean {

String hello() {
return "hello";
}
}
}
Loading

0 comments on commit 2db13aa

Please sign in to comment.