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

Introduce adding 'shared resources' programmatically #3889

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

VladimirDmitrienko
Copy link

@VladimirDmitrienko VladimirDmitrienko commented Jul 16, 2024

Solves: #2677

Overview

Introduce adding shared resources programmatically via @ResourceLock(providers = ...) attribute
which accepts an array of one or more classes implementing ResourceLocksProvider interface.

It allows to add resources in runtime (immediately before starting execution on the engine level).

Resources can be distributed based on any test class / nested test class / test method attributes (e.g. package name, class / method name etc.) or any other user logic.

This approach serves as a more flexible and less verbose alternative for cases in which:

  • adding lots of @ResourceLock(value, mode) annotations manually may be inconvenient;
  • shared resources are not known until runtime.

I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

@VladimirDmitrienko VladimirDmitrienko marked this pull request as draft July 17, 2024 06:39
@VladimirDmitrienko VladimirDmitrienko changed the title Draft: Implement adding ExclusiveResource programmatically. Implement adding ExclusiveResource programmatically. Jul 17, 2024
@VladimirDmitrienko VladimirDmitrienko force-pushed the #2677_add_exclusive_resource_programmatically branch from 50377ba to 194832d Compare July 22, 2024 15:43
@VladimirDmitrienko VladimirDmitrienko changed the title Implement adding ExclusiveResource programmatically. Introduce adding 'shared resources' programmatically Aug 6, 2024
@VladimirDmitrienko VladimirDmitrienko force-pushed the #2677_add_exclusive_resource_programmatically branch 3 times, most recently from c0b3d77 to a90636d Compare August 6, 2024 20:30
@VladimirDmitrienko VladimirDmitrienko marked this pull request as ready for review August 8, 2024 06:52
@VladimirDmitrienko VladimirDmitrienko force-pushed the #2677_add_exclusive_resource_programmatically branch from 3e1d2a6 to 7d894c5 Compare August 8, 2024 07:07
@marcphilipp marcphilipp linked an issue Aug 9, 2024 that may be closed by this pull request
Copy link
Member

@marcphilipp marcphilipp left a comment

Choose a reason for hiding this comment

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

Looks very promising! Sorry for the late request to change the API.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface ResourceLocksFrom {
Copy link
Member

Choose a reason for hiding this comment

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

After discussion in the team, we'd prefer adding a Class<? extends ResourceLocksProvider>[] providers() default {} attribute to the existing @ResourceLock annotation (and making value() optional by giving it an empty default value).

Copy link
Author

Choose a reason for hiding this comment

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

Updated.

[source,java]
.Declaring shared resources provider with `{ResourceLocksFrom}` annotation
----
include::{testDir}/example/SharedResourcesWithResourceLocksFromAnnotationDemo.java[tags=user_guide]
Copy link
Member

Choose a reason for hiding this comment

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

We should also mention here (and in the annotations' Javadoc) that "static" and "dynamic" locks are combined.

Copy link
Author

Choose a reason for hiding this comment

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

Updated.
Mentioned in Javadoc for @ResourceLock and ResourceLocksProvider.

@VladimirDmitrienko
Copy link
Author

Hi @marcphilipp
Maybe you have any updates regarding this PR?

Comment on lines 193 to 194
getExclusiveResourcesFromAnnotationValue(element),
getExclusiveResourcesFromAnnotationProviders(element, providerToLocks)
Copy link
Member

Choose a reason for hiding this comment

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

We should avoid searching for the annotation twice here

Copy link
Author

Choose a reason for hiding this comment

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

Done

{ResourceLock}, they would be _flaky_. Sometimes they would pass, and at other times they
would fail due to the inherent race condition of writing and then reading the same JVM
System Property.
Since Junit Jupiter 5.12 `{ResourceLock}` annotation has 'providers' attribute
Copy link
Member

Choose a reason for hiding this comment

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

Since the User Guide is versioned, we usually avoid mentioning specific versions when features were introduced. Thus, please remove that bit.

Copy link
Author

Choose a reason for hiding this comment

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

Got it. Done

This guarantee extends to lifecycle methods of a test class or method.
For example, if a test method is annotated with a `{ResourceLock}` annotation,
the "lock" will be acquired before any `@BeforeEach` methods are executed
and released after all `@AfterEach` methods have been executed.
Copy link
Member

Choose a reason for hiding this comment

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

Please revert the changes made to the formatting of these two paragraphs.

Copy link
Author

Choose a reason for hiding this comment

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

Done

@Override
public Set<Lock> provideForMethod(Class<?> testClass, Method testMethod) {
ResourceAccessMode mode;
if (testMethod.getName().equals("customPropertyIsNotSetByDefault")) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it would make for a better example if we'd check for testMethod.getName().startsWith("canSet") and invert the if?

Copy link
Author

Choose a reason for hiding this comment

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

Good point.
Done

providerToLocks
);
Stream<ExclusiveResource> fromProvidersInClassLevelAnnotation = getExclusiveResourcesFromAnnotationProviders(
getTestClass(),
Copy link
Member

Choose a reason for hiding this comment

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

What about @Nested test classes? I think we'll have to collect exclusive resources from providers from the test class and, if it's a @Nested one, also from its enclosing class, recursively.

Copy link
Member

Choose a reason for hiding this comment

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

On second thought, there is no need to apply locks from the class level to methods as it is redundant. When executing the method, the class-level locks are already held.

Copy link
Author

Choose a reason for hiding this comment

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

What about @Nested test classes? I think we'll have to collect exclusive resources from providers from the test class and, if it's a @Nested one, also from its enclosing class, recursively.

I suppose that the behaviour of ResourceLocksProvider should be similar to the plain @ResourceLock("...").
Let's consider such an example:

@ResourceLock("A")
static class TestClass {

    @Nested
    @ResourceLock("B")
    class NestedTestClass {
    }
}

NestedTestClass gets only B but does not get A.

That's why I'm not sure that @Nested classes should invoke providers of enclosing classes.

Copy link
Member

Choose a reason for hiding this comment

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

Please disregard my initial comment. We should neither check the test class nor any of its enclosing classes since that's already being done by the parent test descriptors. Thus, please remove the extra code that sets fromProvidersInClassLevelAnnotation etc.

Copy link
Author

Choose a reason for hiding this comment

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

If we remove this code then LocksProvider#provideForMethod() will not be invoked for test():

@ResourceLock(providers = LocksProvider.class)
static class TestClass {

    @Test
    void test() {
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add ExclusiveResource programmatically
2 participants