From dc7cdef33771a053d6d625fda0cc9b50d05de40f Mon Sep 17 00:00:00 2001 From: Karl Heinz Marbaise Date: Mon, 25 Dec 2023 01:39:26 +0100 Subject: [PATCH] Fixed #359 - ITF class level annotations misbehave --- .../release-notes/_release-notes-0.13.0.adoc | 50 +++- .../main/asciidoc/usersguide/usersguide.adoc | 166 +++++++++++- .../AnnotationOnClassAndInheritedIT.java | 240 ++++++++++++++++++ .../examples/profiles/ProfileOnClassIT.java | 4 +- .../on_third_level_nested_class_level/pom.xml | 54 ++++ .../pom.xml | 54 ++++ .../NestedClass/on_nested_class_level/pom.xml | 53 ++++ .../on_class_level/pom.xml | 57 +++++ .../on_method_level/pom.xml | 53 ++++ .../jupiter/extension/AnnotationHelper.java | 48 ++-- 10 files changed, 752 insertions(+), 27 deletions(-) create mode 100644 itf-examples/src/test/java/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT.java create mode 100644 itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/ThirdLevel/on_third_level_nested_class_level/pom.xml create mode 100644 itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/on_second_level_nested_class_level/pom.xml create mode 100644 itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/on_nested_class_level/pom.xml create mode 100644 itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_class_level/pom.xml create mode 100644 itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_method_level/pom.xml diff --git a/itf-documentation/src/main/asciidoc/release-notes/_release-notes-0.13.0.adoc b/itf-documentation/src/main/asciidoc/release-notes/_release-notes-0.13.0.adoc index 4b762616ba..9f1498f050 100644 --- a/itf-documentation/src/main/asciidoc/release-notes/_release-notes-0.13.0.adoc +++ b/itf-documentation/src/main/asciidoc/release-notes/_release-notes-0.13.0.adoc @@ -34,6 +34,7 @@ :issue-351: https://github.com/khmarbaise/maven-it-extension/issues/351[Fixed #351] :issue-353: https://github.com/khmarbaise/maven-it-extension/issues/353[Fixed #353] :issue-355: https://github.com/khmarbaise/maven-it-extension/issues/355[Fixed #355] +:issue-359: https://github.com/khmarbaise/maven-it-extension/issues/359[Fixed #359] :issue-360: https://github.com/khmarbaise/maven-it-extension/issues/360[Fixed #360] :issue-362: https://github.com/khmarbaise/maven-it-extension/issues/362[Fixed #362] :issue-364: https://github.com/khmarbaise/maven-it-extension/issues/364[Fixed #363] @@ -94,11 +95,56 @@ Also all Maven Version for `M3_0_5` up to `M3_5_4` have been marked deprecated to indicate that those versions are very old and shouldn't be used anymore; Not even in tests (You should upgrade your Maven version) + * {issue-359} - ITF class level annotations misbehave + ** if you have used `@MavenProfile` annotation on class level with profiles and relied on the behaviour that + on a method or a nested class a definition of another `@MavenProfile` replaced all the original profiles + during the execution. That behaviour has been changed. Please refer to the users guide about `@MavenProfile` + annotation and it particular behaviour. The following example shows the behaviour in release 0.12.0: ++ +[source,java] +---- +@MavenProfile({"profile-1", "profile-2", "profile-3"}) +class ProfileOnClassIT { + + @MavenTest + void profile_1_2_3(MavenExecutionResult result) { + ... + } + + @MavenTest + @MavenProfile("profile-1") + void profile_1(MavenExecutionResult result) { + ... + } +} +---- ++ +The way to achieve the same with the release 0.13.0, you have to express is like this: ++ +[source,java] +---- +@MavenProfile({"profile-1"}) +class ProfileOnClassIT { + + @MavenTest + @MavenProfile({"profile-2", "profile-3"}) + void profile_1_2_3(MavenExecutionResult result) { + ... + } + + @MavenTest + void profile_1(MavenExecutionResult result) { + ... + } +} +---- + + *Reporter of this release* - * ??? - ** Reported .. + * {issue-359} + ** https://github.com/cstamas[Tamas Cservenak] has reported the issue. *Contributors of this release* diff --git a/itf-documentation/src/main/asciidoc/usersguide/usersguide.adoc b/itf-documentation/src/main/asciidoc/usersguide/usersguide.adoc index 683884e80b..e63339baf7 100644 --- a/itf-documentation/src/main/asciidoc/usersguide/usersguide.adoc +++ b/itf-documentation/src/main/asciidoc/usersguide/usersguide.adoc @@ -553,6 +553,46 @@ class BasicIT { The `+` prefix is responsible for deactivating a given profile during the build. We strongly recommend to use `+` sign (instead of `!`) to prevent issues on different operating systems. +So finally let use show how the inheritance behaviour of the `MavenProfile` annotation is defined. + +[source,java] +---- +@MavenJupiterExtension +@MavenProfile({"run-its"}) +class BasicIT { + + @MavenTest + @MavenProfile("run-its-delta") + void first(MavenExecutionResult result) { + .. + } + + @MavenTest + void second(MavenExecutionResult result) { + .. + } + + @Nested + @MavenProfile("nested-class") + class NestedClass { + @MavenTest + @MavenProfile("nested-first") + void nested_first(MavenExecutionResult result) { + .. + } + @MavenTest + void nested_second(MavenExecutionResult result) { + .. + } + } +} +---- +So based on the given example the method `first` will be executed with the activation of the profiles +`run-its` and `run-its-delta`. The method `second` will be execute with the activated profile `run-its`. The method +`nested_first` will be executed with the activated profiles `run-its`, `nested-class`and `nested-first`. Finally the +method `nested_second` will be executed with the activated profiles `run-its` and `nested-class`. + + [[structuring.system.properties]] === System Properties @@ -662,6 +702,54 @@ class RunningIT ---- This prevents the repetition of the property on each test case method. + +Another option is to define the `SystemProperty` annotation on different levels. For example +class level, method level or nested levels. Let us make deep dive into the following example: +[source,java] +---- +.. +@MavenJupiterExtension +@SystemProperty("skipTests") +class RunningIT +{ + + @MavenTest + void first( MavenExecutionResult result ) + { + ... + } + + @MavenTest + @SystemProperty("second") + void second( MavenExecutionResult result ) + { + ... + } + + @Nested + @SystemProperty("NestedLevel") + class NestedLevel { + @MavenTest + void nested_first( MavenExecutionResult result ) + { + ... + } + + @MavenTest + @SystemProperty("NestedLevelSecond") + void nested_second( MavenExecutionResult result ) + { + ... + } + + } +} +---- +The method `first` will be executed with the defined system property `skipTests` given by the class level `RunningIT`. +The method `second` will be executed with the defined system properties `skipTests` and also with the `second` property. +The method `nested_first` will be executed with the following system properties `skipTests` and `NestedLevel` while the +method `nested_second` will be executed with the following system properties `skipTest`, `NestedLevel` and `NestedLevelSecond`. + [[goals.command.line.options]] === Command Line Options In different scenarios it is needed to define command line options for example `--non-recursive` etc. @@ -770,6 +858,43 @@ class BasicIT { Now let us summarize the information for goals. As long as no explicit `@MavenGoal` is defined the default will be used which is `package`. +Furthermore we can define goal annotation `MavenGoal` on different levels which means class level, method level, +nested classes etc. We will look at the following example: + +[source,java] +---- +@MavenJupiterExtension +@MavenGoal("clean") +class BasicIT { + + @MavenTest + void first(MavenExecutionResult result) { + } + + @MavenTest + @MavenGoal("install") + void second(MavenExecutionResult result) { + } + + @Nested + @MavenGoal("verify") + class NestedLevel { + @MavenTest + void nested_first(MavenExecutionResult result) { + } + @MavenTest + @MavenGoal("site:stage") + void nested_second(MavenExecutionResult result) { + } + + } +} +---- +How would the above test cases executed related to the defined goal annotation? The method `first` will +be executed with the goal `clean`. The method `second` will be executed with the goals `clean` and `install`. +The method `nested_first` in the nested class `NestedLevel` will be executed with the goals `clean` and `verify` +and finally the method `nested_second` will be exectuted with the goals `clean`, `verify` and `site:stage`. + [[structuring.options]] ==== Options Now let us going back to the example: @@ -804,7 +929,7 @@ class BasicIT { } ---- Using the option like this means (like for goals as mentioned before) all consecutive tests will -now run with the given option which in this case is only a single command line option `--debug` +now run with the given option which in this case is only a single command line option `--verbose` and nothing else. You can add supplemental command line options just by adding supplemental annotations to your tests like this: [source,java] @@ -852,6 +977,45 @@ default will be used which are the following: * `--batch-mode` * `--errors` +What will happen if you have a test class like this with nested class and the appropriate annotations: +[source,java] +---- +@MavenJupiterExtension +@MavenOption(MavenCLIOptions.VERBOSE) +class BasicIT { + + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_AT_END) + void first(MavenExecutionResult result) { + ... + } + @MavenTest + void second(MavenExecutionResult result) { + ... + } + + @MavenOption(MavenCLIOptions.FAIL_FAST) + class NestedClass { + @MavenTest + void nested_first(MavenExecutionResult result) { + ... + } + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_NEVER) + void nested_second(MavenExecutionResult result) { + ... + } + + } +} +---- +Let us analyze that. The method `second` will be executed with the option `--verbose` while the +method `first` will be executed with two options `--verbose` and `--fail-at-end`. Now the interesting part. +The method `nested_first` will be executed with the options `--verbose` plus the `--fail-fast` and the method +`nested_second` will be executed with `--verbose`, `--fail-fast` and `--fail-never`. Just ignore that the +combination of those options does not really makes sense. It's only to show the behaviour over different inheritance +levels. In other words the behaviour of the `MavenOption` annotation is additive (except for the root level!). + [[structuring.combined.annotations]] ==== Combined Annotation Sometimes you would like to combine things from options and goals in a more convenient way. diff --git a/itf-examples/src/test/java/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT.java b/itf-examples/src/test/java/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT.java new file mode 100644 index 0000000000..13ed3cee12 --- /dev/null +++ b/itf-examples/src/test/java/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT.java @@ -0,0 +1,240 @@ +package com.soebes.itf.examples; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import com.soebes.itf.jupiter.extension.MavenCLIOptions; +import com.soebes.itf.jupiter.extension.MavenGoal; +import com.soebes.itf.jupiter.extension.MavenJupiterExtension; +import com.soebes.itf.jupiter.extension.MavenOption; +import com.soebes.itf.jupiter.extension.MavenProfile; +import com.soebes.itf.jupiter.extension.MavenTest; +import com.soebes.itf.jupiter.extension.SystemProperty; +import com.soebes.itf.jupiter.maven.MavenExecutionResult; +import org.junit.jupiter.api.Nested; + +import static com.soebes.itf.extension.assertj.MavenITAssertions.assertThat; + +/** + * Checking how annotations like {@link MavenOption}, {@link MavenGoal}, {@link MavenProfile} and {@link SystemProperty} + * are applied to class- method or nested class levels. + * + * @author Karl Heinz Marbaise + */ +@MavenJupiterExtension +@MavenGoal({"help:evaluate"}) +@SystemProperty(value = "expression", content = "project.version") +@SystemProperty("forceStdout") +@MavenOption(MavenCLIOptions.NON_RECURSIVE) +@MavenOption(MavenCLIOptions.QUIET) +@MavenProfile("pbase") +class AnnotationOnClassAndInheritedIT { + + @MavenTest + @MavenProfile("on_class_level") + void on_class_level(MavenExecutionResult result) { + assertThat(result) + .isSuccessful() + .out() + .plain() + .contains("1.0-SNAPSHOT"); + assertThat(result) + .isSuccessful() + .out() + .warn().isEmpty(); + assertThat(result) + .isSuccessful() + .out() + .debug().isEmpty(); + + var argumentsLog = result.getMavenProjectResult().getTargetBaseDirectory().resolve("mvn-arguments.log"); + + assertThat(argumentsLog) + .content() + .containsSubsequence( + "-Pon_class_level,pbase", + "-Dexpression=project.version", + "-DforceStdout", + "--non-recursive", + "--quiet", + "help:evaluate" + ); + } + + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_AT_END) + @SystemProperty("anotherProperty") + @MavenProfile("pmethod") + void on_method_level(MavenExecutionResult result) { + assertThat(result) + .isSuccessful() + .out() + .plain() + .contains("1.0-SNAPSHOT"); + assertThat(result) + .isSuccessful() + .out() + .warn().isEmpty(); + assertThat(result) + .isSuccessful() + .out() + .debug().isEmpty(); + var argumentsLog = result.getMavenProjectResult().getTargetBaseDirectory().resolve("mvn-arguments.log"); + + assertThat(argumentsLog) + .content() + .containsSubsequence( + "-Ppmethod,pbase", + "-DanotherProperty", + "-Dexpression=project.version", + "-DforceStdout", + "--fail-at-end", + "--non-recursive", + "--quiet", + "help:evaluate" + ); + } + + @Nested + @SystemProperty("NestedClass") + @MavenProfile("pNestedClass") + class NestedClass { + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_NEVER) + @SystemProperty("anotherPropertyOnNestedClass") + @SystemProperty("on_nested_class_level") + void on_nested_class_level(MavenExecutionResult result) { + assertThat(result) + .isSuccessful() + .out() + .plain() + .contains("1.0-SNAPSHOT"); + assertThat(result) + .isSuccessful() + .out() + .warn().isEmpty(); + assertThat(result) + .isSuccessful() + .out() + .debug().isEmpty(); + var argumentsLog = result.getMavenProjectResult().getTargetBaseDirectory().resolve("mvn-arguments.log"); + + assertThat(argumentsLog) + .content() + .containsSubsequence( + "-PpNestedClass,pbase", + "-DanotherPropertyOnNestedClass", + "-Don_nested_class_level", + "-DNestedClass", + "-Dexpression=project.version", + "-DforceStdout", + "--fail-never", + "--non-recursive", + "--quiet", + "help:evaluate" + ); + } + + @Nested + @MavenOption(MavenCLIOptions.ERRORS) + @SystemProperty("SecondLevel") + class SecondLevel { + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_AT_END) + @SystemProperty("anotherPropertyOnSecondLevelNestedClass") + void on_second_level_nested_class_level(MavenExecutionResult result) { + assertThat(result) + .isSuccessful() + .out() + .plain() + .contains("1.0-SNAPSHOT"); + assertThat(result) + .isSuccessful() + .out() + .warn().isEmpty(); + assertThat(result) + .isSuccessful() + .out() + .debug().isEmpty(); + var argumentsLog = result.getMavenProjectResult().getTargetBaseDirectory().resolve("mvn-arguments.log"); + + assertThat(argumentsLog) + .content() + .containsSubsequence( + "-PpNestedClass,pbase", + "-DanotherPropertyOnSecondLevelNestedClass", + "-DSecondLevel", + "-DNestedClass", + "-Dexpression=project.version", + "-DforceStdout", + "--fail-at-end", + "--errors", + "--non-recursive", + "--quiet", + "help:evaluate" + ); + } + + @Nested + @SystemProperty("ThirdLevel") + @MavenProfile("pThirdLevel") + class ThirdLevel { + @MavenTest + @MavenOption(MavenCLIOptions.FAIL_NEVER) + @MavenGoal("help:system") + @SystemProperty("anotherPropertyOnSecondLevelNestedClass") + void on_third_level_nested_class_level(MavenExecutionResult result) { + assertThat(result) + .isSuccessful() + .out() + .plain() + .contains("1.0-SNAPSHOT"); + assertThat(result) + .isSuccessful() + .out() + .warn().isEmpty(); + assertThat(result) + .isSuccessful() + .out() + .debug().isEmpty(); + var argumentsLog = result.getMavenProjectResult().getTargetBaseDirectory().resolve("mvn-arguments.log"); + + assertThat(argumentsLog) + .content() + .containsSubsequence( + "-PpThirdLevel,pNestedClass,pbase", + "-DanotherPropertyOnSecondLevelNestedClass", + "-DThirdLevel", + "-DSecondLevel", + "-Dexpression=project.version", + "-DforceStdout", + "--fail-never", + "--errors", + "--non-recursive", + "--quiet", + "help:system", + "help:evaluate" + ); + } + + } + } + } + +} \ No newline at end of file diff --git a/itf-examples/src/test/java/com/soebes/itf/examples/profiles/ProfileOnClassIT.java b/itf-examples/src/test/java/com/soebes/itf/examples/profiles/ProfileOnClassIT.java index 60fe5ce241..3f9176f228 100644 --- a/itf-examples/src/test/java/com/soebes/itf/examples/profiles/ProfileOnClassIT.java +++ b/itf-examples/src/test/java/com/soebes/itf/examples/profiles/ProfileOnClassIT.java @@ -28,10 +28,11 @@ import static com.soebes.itf.extension.assertj.MavenITAssertions.assertThat; @MavenJupiterExtension -@MavenProfile({"profile-1", "profile-2", "profile-3"}) +@MavenProfile("profile-1") class ProfileOnClassIT { @MavenTest + @MavenProfile({"profile-2", "profile-3"}) void profile_1_2_3(MavenExecutionResult result) { assertThat(result) .isSuccessful() @@ -50,7 +51,6 @@ void profile_1_2_3(MavenExecutionResult result) { @MavenTest @DisplayName("This tests shows that the profile on the method level takes precedence over the mavenProfile on the class level.") - @MavenProfile("profile-1") void profile_1(MavenExecutionResult result) { assertThat(result) .isSuccessful() diff --git a/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/ThirdLevel/on_third_level_nested_class_level/pom.xml b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/ThirdLevel/on_third_level_nested_class_level/pom.xml new file mode 100644 index 0000000000..0971dc91bc --- /dev/null +++ b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/ThirdLevel/on_third_level_nested_class_level/pom.xml @@ -0,0 +1,54 @@ + + + + + + + 4.0.0 + + + com.soebes.smpp + smpp + 6.0.4 + + + com.soebes.katas + kata-fraction + 1.0-SNAPSHOT + + + + pbase + + + pmethod + + + pNestedClass + + + pThirdLevel + + + + diff --git a/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/on_second_level_nested_class_level/pom.xml b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/on_second_level_nested_class_level/pom.xml new file mode 100644 index 0000000000..0971dc91bc --- /dev/null +++ b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/SecondLevel/on_second_level_nested_class_level/pom.xml @@ -0,0 +1,54 @@ + + + + + + + 4.0.0 + + + com.soebes.smpp + smpp + 6.0.4 + + + com.soebes.katas + kata-fraction + 1.0-SNAPSHOT + + + + pbase + + + pmethod + + + pNestedClass + + + pThirdLevel + + + + diff --git a/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/on_nested_class_level/pom.xml b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/on_nested_class_level/pom.xml new file mode 100644 index 0000000000..4183bb4011 --- /dev/null +++ b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/NestedClass/on_nested_class_level/pom.xml @@ -0,0 +1,53 @@ + + + + + + + 4.0.0 + + + com.soebes.smpp + smpp + 6.0.4 + + + com.soebes.katas + kata-fraction + 1.0-SNAPSHOT + + + + pbase + + + pmethod + + + pNestedClass + + + pThirdLevel + + + diff --git a/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_class_level/pom.xml b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_class_level/pom.xml new file mode 100644 index 0000000000..3768f7db6d --- /dev/null +++ b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_class_level/pom.xml @@ -0,0 +1,57 @@ + + + + + + + 4.0.0 + + + com.soebes.smpp + smpp + 6.0.4 + + + com.soebes.katas + kata-fraction + 1.0-SNAPSHOT + + + + pbase + + + pmethod + + + on_class_level + + + pNestedClass + + + pThirdLevel + + + + diff --git a/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_method_level/pom.xml b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_method_level/pom.xml new file mode 100644 index 0000000000..4183bb4011 --- /dev/null +++ b/itf-examples/src/test/resources-its/com/soebes/itf/examples/AnnotationOnClassAndInheritedIT/on_method_level/pom.xml @@ -0,0 +1,53 @@ + + + + + + + 4.0.0 + + + com.soebes.smpp + smpp + 6.0.4 + + + com.soebes.katas + kata-fraction + 1.0-SNAPSHOT + + + + pbase + + + pmethod + + + pNestedClass + + + pThirdLevel + + + diff --git a/itf-jupiter-extension/src/main/java/com/soebes/itf/jupiter/extension/AnnotationHelper.java b/itf-jupiter-extension/src/main/java/com/soebes/itf/jupiter/extension/AnnotationHelper.java index f2e9ef8355..00ce765b11 100644 --- a/itf-jupiter-extension/src/main/java/com/soebes/itf/jupiter/extension/AnnotationHelper.java +++ b/itf-jupiter-extension/src/main/java/com/soebes/itf/jupiter/extension/AnnotationHelper.java @@ -54,14 +54,17 @@ static boolean hasProfiles(ExtensionContext context) { * @return The stream with the profiles. */ static List profiles(ExtensionContext context) { - List mavenProfilesOnTestMethod = AnnotationSupport.findRepeatableAnnotations(context.getTestMethod(), MavenProfile.class); - List profiles = mavenProfilesOnTestMethod.stream().flatMap(profile -> Stream.of(profile.value())).collect(Collectors.toList()); - if (!profiles.isEmpty()) { - return profiles; + Method method = context.getTestMethod().orElseThrow(IllegalStateException::new); + + List profiles = new ArrayList<>(AnnotationSupport.findRepeatableAnnotations(method, MavenProfile.class)); + + List allInstances = context.getTestInstances().orElseThrow(IllegalStateException::new).getAllInstances(); + for (int i = allInstances.size()-1; i >=0; i--) { + List repeatableAnnotations = AnnotationSupport.findRepeatableAnnotations(allInstances.get(i).getClass(), MavenProfile.class); + profiles.addAll(repeatableAnnotations); } - List repeatableAnnotationsOnClass = AnnotationSupport.findRepeatableAnnotations(context.getTestClass(), MavenProfile.class); - return repeatableAnnotationsOnClass.stream().flatMap(profile -> Stream.of(profile.value())).collect(Collectors.toList()); + return profiles.stream().flatMap(profile -> Stream.of(profile.value())).collect(Collectors.toList()); } /** * @param context {@link ExtensionContext} @@ -79,14 +82,16 @@ static boolean hasGoals(ExtensionContext context) { * @return The stream with the goals. */ static List goals(ExtensionContext context) { - List goalAnnotations = AnnotationSupport.findRepeatableAnnotations(context.getTestMethod(), MavenGoal.class); - List stringStream = goalAnnotations.stream().flatMap(goal -> Stream.of(goal.value())).collect(Collectors.toList()); - if (!stringStream.isEmpty()) { - return stringStream; + Method method = context.getTestMethod().orElseThrow(IllegalStateException::new); + + List goalAnnotations = new ArrayList<>(AnnotationSupport.findRepeatableAnnotations(method, MavenGoal.class)); + + List allInstances = context.getTestInstances().orElseThrow(IllegalStateException::new).getAllInstances(); + for (int i = allInstances.size()-1; i >=0; i--) { + goalAnnotations.addAll(AnnotationSupport.findRepeatableAnnotations(allInstances.get(i).getClass(), MavenGoal.class)); } - List repeatableAnnotationsOnClass = AnnotationSupport.findRepeatableAnnotations(context.getTestClass(), MavenGoal.class); - return repeatableAnnotationsOnClass.stream().flatMap(goal -> Stream.of(goal.value())).collect(Collectors.toList()); + return goalAnnotations.stream().flatMap(goal -> Stream.of(goal.value())).collect(Collectors.toList()); } /** @@ -98,23 +103,22 @@ static boolean hasOptions(ExtensionContext context) { } /** - * Get the options from the annotation either on test method level - * or on test class level. + * Get the options from the class level, method level or nested class level. * * @param context {@link ExtensionContext} * @return The stream with the options. */ static List options(ExtensionContext context) { - List mavenOptionsOnTestMethod = AnnotationSupport.findRepeatableAnnotations(context.getTestMethod(), MavenOption.class); - List options = mavenOptionsOnTestMethod.stream() - .flatMap(option -> option.parameter().isEmpty() ? Stream.of(option.value()) : Stream.of(option.value(), option.parameter())) - .collect(Collectors.toList()); - if (!options.isEmpty()) { - return options; + Method method = context.getTestMethod().orElseThrow(IllegalStateException::new); + + List options = new ArrayList<>(AnnotationSupport.findRepeatableAnnotations(method, MavenOption.class)); + + List allInstances = context.getTestInstances().orElseThrow(IllegalStateException::new).getAllInstances(); + for (int i = allInstances.size()-1; i >=0; i--) { + options.addAll(AnnotationSupport.findRepeatableAnnotations(allInstances.get(i).getClass(), MavenOption.class)); } - List mavenOptionsOnTestClass = AnnotationSupport.findRepeatableAnnotations(context.getTestClass(), MavenOption.class); - return mavenOptionsOnTestClass.stream().flatMap(option -> option.parameter().isEmpty() ? Stream.of(option.value()) : Stream.of(option.value(), option.parameter())) + return options.stream().flatMap(option -> option.parameter().isEmpty() ? Stream.of(option.value()) : Stream.of(option.value(), option.parameter())) .collect(Collectors.toList()); }