Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inherited test methods don't support their annotations (fixes #1032) #1033

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.qameta.allure.util.ReflectionUtils.getAllAnnotations;
import static io.qameta.allure.util.ResultsUtils.createLabel;
import static io.qameta.allure.util.ResultsUtils.createLink;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -116,7 +117,7 @@ public static Set<Link> getLinks(final Collection<Annotation> annotations) {
* @return discovered labels.
*/
public static Set<Label> getLabels(final AnnotatedElement annotatedElement) {
return getLabels(annotatedElement.getAnnotations());
return getLabels(getAllAnnotations(annotatedElement));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be added to getLinks method as well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add test

}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2016-2024 Qameta Software Inc
*
* 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.qameta.allure.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;

public final class ReflectionUtils {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add javadoc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add tests

private ReflectionUtils() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add throw new IllegalStateException("Do not instance");

}

/**
* The function gives back all the methods that users have declared in subclasses and interfaces.
*
* @param clazz An introspecting class.
* @return All a user declared methods.
*/
public static List<Method> getAllDeclaredMethods(final Class<?> clazz) {
final List<Method> methods = new ArrayList<>();

doRecursivelyWith(
clazz,
ReflectionUtils::extractMethods,
methods::add
);

return methods;
}

/**
* The function gives back all the annotations that users have declared in subclasses and interfaces.
*
* @param annotatedElement An introspecting element.
* @return All a user declared annotations.
*/
public static List<Annotation> getAllAnnotations(final AnnotatedElement annotatedElement) {
if (annotatedElement instanceof Class) {
final List<Annotation> annotations = new ArrayList<>();

doRecursivelyWith(
(Class<?>) annotatedElement,
ReflectionUtils::extractAnnotations,
annotations::add
);

return annotations;
} else {
return Arrays.asList(annotatedElement.getAnnotations());
}
}

private static void extractMethods(final Class<?> clazz, final Consumer<Method> callback) {
Arrays.stream(clazz.getDeclaredMethods())
.forEach(callback);

// The default methods might have test declarations, them are needed to introspect.
Arrays.stream(clazz.getInterfaces())
.flatMap(ifc -> Stream.of(ifc.getMethods()))
.filter(Method::isDefault)
.forEach(callback);
}

private static void extractAnnotations(final Class<?> clazz, final Consumer<Annotation> callback) {
Arrays.stream(clazz.getAnnotations())
.forEach(callback);

Arrays.stream(clazz.getInterfaces())
.flatMap(ifc -> Stream.of(ifc.getAnnotations()))
.forEach(callback);
}

private static <T> void doRecursivelyWith(
final Class<?> clazz,
final BiConsumer<Class<?>, Consumer<T>> introspector,
final Consumer<T> resultCallback
) {
if (clazz == Object.class) {
return;
}

introspector.accept(clazz, resultCallback);

if (clazz.getSuperclass() != null && clazz.getSuperclass() != Object.class) {
doRecursivelyWith(clazz.getSuperclass(), introspector, resultCallback);
} else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doRecursivelyWith(superIfc, introspector, resultCallback);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@

import java.lang.reflect.Method;
import java.util.Optional;
import java.util.stream.Stream;

import static io.qameta.allure.util.ReflectionUtils.getAllDeclaredMethods;

/**
* @author charlie (Dmitry Baev).
Expand Down Expand Up @@ -86,12 +87,20 @@ public static Optional<Method> getTestMethod(final TestSource source) {
public static Optional<Method> getTestMethod(final MethodSource source) {
try {
final Class<?> aClass = Class.forName(source.getClassName());
return Stream.of(aClass.getDeclaredMethods())
.filter(method -> MethodSource.from(method).equals(source))

return getAllDeclaredMethods(aClass).stream()
// The filter ignores the class name, as the methods are already defined in an inherited class.
.filter(method -> equalsWithoutClassName(MethodSource.from(method), source))
.findAny();
} catch (ClassNotFoundException e) {
LOGGER.trace("Could not get test method from method source {}", source, e);
}
return Optional.empty();
}

private static boolean equalsWithoutClassName(final MethodSource a, final MethodSource b) {
return a.getMethodName().equals(b.getMethodName())
&& a.getMethodParameterTypes().equals(b.getMethodParameterTypes());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.qameta.allure.junitplatform.features.DisabledTests;
import io.qameta.allure.junitplatform.features.DynamicTests;
import io.qameta.allure.junitplatform.features.FailedTests;
import io.qameta.allure.junitplatform.features.InheritedTests;
import io.qameta.allure.junitplatform.features.JupiterUniqueIdTest;
import io.qameta.allure.junitplatform.features.MarkerAnnotationSupport;
import io.qameta.allure.junitplatform.features.NestedTests;
Expand Down Expand Up @@ -874,6 +875,44 @@ void shouldSetDifferentUuidsInDifferentRuns() {

}

@Test
void shouldInheritedTestAnnotations() {
final AllureResults allureResults = runClasses(InheritedTests.class);

TestResult grandparentTest = allureResults.getTestResultByName("grandparentTest()");
assertThat(grandparentTest.getLabels())
.extracting(Label::getName, Label::getValue)
.contains(
tuple("epic", InheritedTests.INHERITED_TEST_EPIC),
tuple("feature", InheritedTests.INHERITED_TEST_FUTURE),
tuple("story", InheritedTests.INHERITED_TEST_GRANDPARENT_STORY)
);
assertThat(grandparentTest.getDescription()).isEqualTo(InheritedTests.TEST_DESCRIPTION);
assertThat(grandparentTest.getLinks()).extracting(Link::getName).contains(InheritedTests.TEST_LINK);

TestResult parentTest = allureResults.getTestResultByName("parentTest()");
assertThat(parentTest.getLabels())
.extracting(Label::getName, Label::getValue)
.contains(
tuple("epic", InheritedTests.INHERITED_TEST_EPIC),
tuple("feature", InheritedTests.INHERITED_TEST_FUTURE),
tuple("story", InheritedTests.INHERITED_TEST_PARENT_STORY)
);
assertThat(parentTest.getDescription()).isEqualTo(InheritedTests.TEST_DESCRIPTION);
assertThat(parentTest.getLinks()).extracting(Link::getName).contains(InheritedTests.TEST_LINK);

TestResult childTest = allureResults.getTestResultByName("childTest()");
assertThat(childTest.getLabels())
.extracting(Label::getName, Label::getValue)
.contains(
tuple("epic", InheritedTests.INHERITED_TEST_EPIC),
tuple("feature", InheritedTests.INHERITED_TEST_FUTURE),
tuple("story", InheritedTests.INHERITED_TEST_CHILD_STORY)
);
assertThat(childTest.getDescription()).isEqualTo(InheritedTests.TEST_DESCRIPTION);
assertThat(childTest.getLinks()).extracting(Link::getName).contains(InheritedTests.TEST_LINK);
}

@Test
void shouldSupportNestedClasses() {
final AllureResults allureResults = runClasses(NestedTests.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2016-2024 Qameta Software Inc
*
* 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.qameta.allure.junitplatform.features;

import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import io.qameta.allure.Link;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class InheritedTests {
public static final String INHERITED_TEST_EPIC = "Inherited epic";
public static final String INHERITED_TEST_FUTURE = "Inherited future";
public static final String INHERITED_TEST_GRANDPARENT_STORY = "Inherited grandparent story";

public static final String INHERITED_TEST_PARENT_STORY = "Inherited parent story";

public static final String INHERITED_TEST_CHILD_STORY = "Inherited child story";

public static final String TEST_DESCRIPTION = "Test description";

public static final String TEST_LINK = "Test link";

@Epic(INHERITED_TEST_EPIC)
@Feature(INHERITED_TEST_FUTURE)
public interface GrandparentTest {
@Test
@Description(TEST_DESCRIPTION)
@Story(INHERITED_TEST_GRANDPARENT_STORY)
@Link(TEST_LINK)
default void grandparentTest() {
}
}

public abstract static class ParentTest implements GrandparentTest {
@Test
@Description(TEST_DESCRIPTION)
@Story(INHERITED_TEST_PARENT_STORY)
@Link(TEST_LINK)
void parentTest() {
}

}

@Nested
class ChildTest extends ParentTest {
@Test
@Description(TEST_DESCRIPTION)
@Story(INHERITED_TEST_CHILD_STORY)
@Link(TEST_LINK)
void childTest() {
}
}
}
Loading