Skip to content

Commit

Permalink
Document method and field inheritance semantics
Browse files Browse the repository at this point in the history
See #3553
  • Loading branch information
sbrannen committed Apr 7, 2024
1 parent ef17fc0 commit 656b9ed
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ repository on GitHub.
[[release-notes-5.11.0-M1-junit-platform-bug-fixes]]
==== Bug Fixes

* Field and method search algorithms now adhere to standard Java semantics regarding
whether a given field or method is visible or overridden according to the rules of the
Java language. See the new
<<../user-guide/index.adoc#extensions-supported-utilities-search-semantics, Field and
Method Search Semantics>> section of the User Guide for details.
* `ReflectionSupport.findFields(...)` now returns a distinct set of fields.
* Fixed parsing of recursive jar URIs which allows the JUnit Platform Launcher to be used
inside Spring Boot executable jars for Spring Boot 3.2 and later.
Expand All @@ -25,7 +30,13 @@ repository on GitHub.
[[release-notes-5.11.0-M1-junit-platform-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes

* ❓
* As mentioned in the _Bug Fixes_ section above, field and method search algorithms now
adhere to standard Java semantics regarding whether a given field or method is visible
or overridden according to the rules of the Java language. The changes in the search
algorithms may, however, result in breaking changes for some use cases. In light of
that, it is possible to revert to the previous "legacy semantics". See the new
<<../user-guide/index.adoc#extensions-supported-utilities-search-semantics, Field and
Method Search Semantics>> section of the User Guide for details.

[[release-notes-5.11.0-M1-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
Expand All @@ -43,7 +54,19 @@ repository on GitHub.
[[release-notes-5.11.0-M1-junit-jupiter-bug-fixes]]
==== Bug Fixes

* ❓
* Due to changes in the JUnit Platform regarding field and method search algorithms (see
<<release-notes-5.11.0-M1-junit-platform-bug-fixes>> above), numerous bugs have been
addressed within JUnit Jupiter, including but not limited to the following.
** Two `@TempDir` fields with the same name in a superclass and subclass will now both
be injected.
** Two `@Test` methods with the same signature in a superclass and subclass will now
both be invoked, as long as the `@Test` method in the subclass does not override the
`@Test` method in the superclass, which can occur if the superclass method is `private`
or if the superclass method is package-private and resides in a different package than
the subclass.
*** The same applies to other types of test methods (`@TestFactory`,
`@ParameterizedTest`, etc.) as well as lifecycle methods (`@BeforeAll`,
`@AfterAll`, `@BeforeEach`, and `@AfterEach`).

[[release-notes-5.11.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
Expand Down
107 changes: 84 additions & 23 deletions documentation/src/docs/asciidoc/user-guide/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ using the `@Order` annotation. See the <<extensions-registration-programmatic-or
Extension Registration Order>> tip for `@RegisterExtension` fields for details.
====

[TIP]
.Extension Inheritance
====
Extensions registered declaratively via `@ExtendWith` on fields in superclasses will be
inherited.
See <<extensions-registration-inheritance, Extension Inheritance>> for details.
====

NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on
<<extensions-registration-programmatic-static-fields, Static Fields>> and
<<extensions-registration-programmatic-instance-fields, Instance Fields>> for
Expand Down Expand Up @@ -196,6 +205,15 @@ extensions to be registered last and _after_ callback extensions to be registere
relative to other programmatically registered extensions.
====

[TIP]
.Extension Inheritance
====
Extensions registered via `@RegisterExtension` or `@ExtendWith` on fields in superclasses
will be inherited.
See <<extensions-registration-inheritance, Extension Inheritance>> for details.
====

NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be
either `static` or non-static.

Expand Down Expand Up @@ -304,11 +322,23 @@ support for `TestInfo`, `TestReporter`, etc.).
[[extensions-registration-inheritance]]
==== Extension Inheritance

Registered extensions are inherited within test class hierarchies with top-down
semantics. Similarly, extensions registered at the class-level are inherited at the
method-level. Furthermore, a specific extension implementation can only be registered
once for a given extension context and its parent contexts. Consequently, any attempt to
register a duplicate extension implementation will be ignored.
Registered extensions are inherited within test class hierarchies with top-down semantics.
Similarly, extensions registered at the class-level are inherited at the method-level.
This applies to all extensions, independent of how they are registered (declaratively or
programmatically).

This means that extensions registered declaratively via `@ExtendWith` on a superclass will
be registered before extensions registered declaratively via `@ExtendWith` on a subclass.

Similarly, extensions registered programmatically via `@RegisterExtension` or
`@ExtendWith` on fields in a superclass will be registered before extensions registered
programmatically via `@RegisterExtension` or `@ExtendWith` on fields in a subclass, unless
`@Order` is used to alter that behavior (see <<extensions-registration-programmatic-order,
Extension Registration Order>> for details).

NOTE: A specific extension implementation can only be registered once for a given
extension context and its parent contexts. Consequently, any attempt to register a
duplicate extension implementation will be ignored.

[[extensions-conditions]]
=== Conditional Test Execution
Expand Down Expand Up @@ -704,6 +734,8 @@ and fields in a class or interface. Some of these methods search on implemented
interfaces and within class hierarchies to find annotations. Consult the Javadoc for
`{AnnotationSupport}` for further details.

NOTE: See also: <<extensions-supported-utilities-search-semantics>>

[[extensions-supported-utilities-classes]]
==== Class Support

Expand All @@ -720,6 +752,8 @@ class, and to find and invoke methods. Some of these methods traverse class hier
to locate matching methods. Consult the Javadoc for `{ReflectionSupport}` for further
details.

NOTE: See also: <<extensions-supported-utilities-search-semantics>>

[[extensions-supported-utilities-modifier]]
==== Modifier Support

Expand All @@ -737,6 +771,37 @@ wrapper types, date and time types from the `java.time package`, and some additi
common Java types such as `File`, `BigDecimal`, `BigInteger`, `Currency`, `Locale`, `URI`,
`URL`, `UUID`, etc. Consult the Javadoc for `{ConversionSupport}` for further details.

[[extensions-supported-utilities-search-semantics]]
==== Field and Method Search Semantics

Various methods in `AnnotationSupport` and `ReflectionSupport` use search algorithms that
traverse type hierarchies to locate matching fields and methods – for example,
`AnnotationSupport.findAnnotatedFields(...)`, `ReflectionSupport.findMethods(...)`, etc.

As of JUnit 5.11 (JUnit Platform 1.11), field and method search algorithms adhere to
standard Java semantics regarding whether a given field or method is visible or overridden
according to the rules of the Java language.

Prior to JUnit 5.11, the field and method search algorithms applied what we now refer to
as "legacy semantics". Legacy semantics consider fields and methods to be _hidden_,
_shadowed_, or _superseded_ by fields and methods in super types (superclasses or
interfaces) based solely on the field's name or the method's signature, disregarding the
actual Java language semantics for visibility and the rules that determine if one method
overrides another method.

Although the JUnit team recommends the use of the standard search semantics, developers
may optionally revert to the legacy semantics via the
`junit.platform.reflection.search.useLegacySemantics` JVM system property.

For example, to enable legacy search semantics for fields and methods, you can start your
JVM with the following system property.

`-Djunit.platform.reflection.search.useLegacySemantics=true`

NOTE: Due to the low-level nature of the feature, the
`junit.platform.reflection.search.useLegacySemantics` flag can only be set via a JVM
system property. It cannot be set via a <<running-tests-config-params, configuration
parameter>>.

[[extensions-execution-order]]
=== Relative Execution Order of User Code and Extensions
Expand Down Expand Up @@ -863,31 +928,27 @@ callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_
JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies
for user-supplied _lifecycle methods_ (see <<writing-tests-definitions>>).

* `@BeforeAll` methods are inherited from superclasses as long as they are not _hidden_,
_overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of
Java's visibility rules). Furthermore, `@BeforeAll` methods from superclasses will be
executed **before** `@BeforeAll` methods in subclasses.
* `@BeforeAll` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@BeforeAll` methods from superclasses will be executed
**before** `@BeforeAll` methods in subclasses.
** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they
are not _hidden_ or _overridden_, and `@BeforeAll` methods from an interface will be
executed **before** `@BeforeAll` methods in the class that implements the interface.
* `@AfterAll` methods are inherited from superclasses as long as they are not _hidden_,
_overridden_, or _superseded_ (i.e., replaced based on signature only, irrespective of
Java's visibility rules). Furthermore, `@AfterAll` methods from superclasses will be
executed **after** `@AfterAll` methods in subclasses.
are not _overridden_, and `@BeforeAll` methods from an interface will be executed
**before** `@BeforeAll` methods in the class that implements the interface.
* `@AfterAll` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@AfterAll` methods from superclasses will be executed
**after** `@AfterAll` methods in subclasses.
** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they
are not _hidden_ or _overridden_, and `@AfterAll` methods from an interface will be
executed **after** `@AfterAll` methods in the class that implements the interface.
are not _overridden_, and `@AfterAll` methods from an interface will be executed
**after** `@AfterAll` methods in the class that implements the interface.
* `@BeforeEach` methods are inherited from superclasses as long as they are not
_overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of
Java's visibility rules). Furthermore, `@BeforeEach` methods from superclasses will be
executed **before** `@BeforeEach` methods in subclasses.
_overridden_. Furthermore, `@BeforeEach` methods from superclasses will be executed
**before** `@BeforeEach` methods in subclasses.
** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as
long as they are not _overridden_, and `@BeforeEach` default methods will be executed
**before** `@BeforeEach` methods in the class that implements the interface.
* `@AfterEach` methods are inherited from superclasses as long as they are not
_overridden_ or _superseded_ (i.e., replaced based on signature only, irrespective of
Java's visibility rules). Furthermore, `@AfterEach` methods from superclasses will be
executed **after** `@AfterEach` methods in subclasses.
_overridden_. Furthermore, `@AfterEach` methods from superclasses will be executed
**after** `@AfterEach` methods in subclasses.
** Similarly, `@AfterEach` methods declared as interface default methods are inherited as
long as they are not _overridden_, and `@AfterEach` default methods will be executed
**after** `@AfterEach` methods in the class that implements the interface.
Expand Down
Loading

0 comments on commit 656b9ed

Please sign in to comment.