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
Remove @Inherited from annotations and upgrade utils to find them #697
Comments
I am closing this as the premise is incorrect. |
I'm not sure where the method you have linked sits in the typical flow of an extension finding its annotation. My assumption is that Confirming the intentional behavior, this test shows that If FWIW, I created a separate test that simulates a user composed annotation on a superclass. To make annotations easy to use, I think it would be ideal for this test to work. |
It doesn't matter much, which exact public method from I typed out a full reply why we should keep the code as is and then almost changed my mind. I'll put both arguments here. Keep as is
I agree, but I don't see how that is an argument to change the behavior of extension discovery. As it is, a class-level extension is either present on just the superclass (no
No, the only reason to include And the JLS and Javadoc are pretty clear:
We are aligned with the language semantics.
I think that's the real issue here: Users might forget to correctly apply the language semantics. I don't think it makes sense to accommodate that by (a) violating the semantics ourselves and (b) limiting user's options. ChangeWhat started to change my mind was this:
Why does Jupiter register these extensions? And what's the effect of it. In some cases, a pretty hilarious one: public class Tests implements TestA, TestB { }
interface TestA {
@Test
default void testA() { }
}
@Disabled
interface TestB {
@Test
default void testB() { }
} How many tests get executed?
It's indeed the last option, which is... maybe unexpected? Didn't the user just want to disable the tests in
But what about the language semantics? This described Which brought me to the question what it would even mean for a test annotation to be applied to only the superclass. Because here's the thing: In the end, everything Jupiter does, revolves around method execution. There's no code generation, no information gathering, etc.. Jupiter executes test (and setup/teardown) methods and nothing else. So what would be the value of requiring That leaves as only meaningful place of distinction concrete test classes that also extended. There, requiring SynthesisMy current understanding is that JLS and Jupiter are at odds here. The former requires So which path do we choose? Unless there are compelling arguments for why the loss of expressiveness is an issue, I would prefer to conform with Jupiter. Particularly because what it says about extensions, should also apply to ours:
I will add this to the 2.0 milestone because this feels like an incompatible change. I don't know when I'll have time to make the change and see how it ripples through the code base, so if anybody wants to give it a shot, feel free to do so. |
Wow, thank you for all the analysis on this! IMO, tests happening as a result of default interface methods is a degenerate/unplanned case. I think that possibility opened up when default methods were added to the JLS and the JUnit people never really thought through the implications. Perhaps that should be a JUnit feature request: Throw an error if tests are discovered in an interface's default methods. |
@eeverman I think they have thought about it, actually, but I'm not sure to what extent. I say this because somewhere in this section in the user guide, they describe how to test interface contracts, which is useful for testing common behaviour across a number of units. For example, it can be used to check that two versions of a unit have the same behaviour, if one is doing a big refactoring or following "branch by abstraction". Another example is checking that a specification is followed, like the javadocs for |
First of all: thanks @eeverman for bringing this to our attention! 🙏 I think there are two distinct things to discuss:
When it comes to 2, I believe this is the corresponding change in Jupiter (2017):
From Jupiter's 5.1.0 release notes:
It feels like we should adopt this behavior in When it comes to 1, I'm a bit undecided – mainly because I haven't had enough time to think about it yet. 😅 But anyway, here are our related issues: |
Thank you everyone for the great discussion! I need to correct one key error in my original ticket:
...and in fact, if the Pioneer annotations are all marked as I think that takes the urgency of this issue down a few notches. I still feel like marking the extension registration annotations If JUnit doesn't require |
So I'm making this change... I remove all
Häh? 😕 So suddenly it does make a difference whether an annotations (Btw, this is not just theory. The practical consequence is that environment variable and system property extensions no longer find annotations on super classes.) |
Yes, that's the behaviour |
I kept investigating this and successfully bent my brain into a pretzel. 🥨 I also now believe that the Jupiter user guide is a lying little hobbit. Remember this?
Then why does the test in @Nested
class Tests extends TestSuper { }
@Disabled
@DisplayName("Super")
class TestSuper {
@Test
void testC() { }
} Also, shouldn't the test nodes for both classes be named "Super" (given that the "extension" is "inherited")? 😠 This made me curious and I went looking for type-level annotations that are not inherited:
As for @Nested
class InOrder {
@Nested @Order(1) class TestA { @Test void testA() { } }
@Nested @Order(2) class TestB { @Test void testB() { } }
@Nested class TestC extends Order0 { @Test void testC() { } }
@Order(0) class Order0 { }
}
|
PS: My conclusion is that, in line with the JLS, not |
I am hence inclined to close this ticket. Will take votes on that until Monday afternoon. If we vote to kick it out, we can then release 2.0.0-RC1. |
For completeness' sake: lab/not-inherited |
Sounds reasonable. But regardless of the outcome, let's raise an issue with JUnit 5 so they can prioritise a fix. |
@nipafx thank you for investigating this further! 🙏 As I said earlier, I was undecided about removing Now, if we also don't want to tweak the algorithm of |
This is ultimately related to #696
Remove
@Inherited
from all annotations used to register extensions and upgrade thePioneerAnnotationUtils
to find annotations on superclasses, even if they are not@Inherited
.From JUnit's perspective, there is no requirement that annotations on a test superclass be marked as
@Inherited
. And the meaning is not correct: When an extension-registering annotation is placed on a test superclass, it really is on the superclass and not on the subclass, as seen in the distinct registration order of the extensions (superclass extensions are registered first). The only reason to include@Inherited
on extension-registering annotation is because thePioneerAnnotationUtils.findClosestEnclosingAnnotation
doesn't currently find annotations on superclasses without it.The tie-in to #696
If users are allowed to compose Pioneer annotations into their own custom annotations (#696), then it is very easy for users to forget to include the
@Inherited
marker. This issue really needs to be resolved for 696 to be workable w/out a lot of users running into issues.The text was updated successfully, but these errors were encountered: