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

Provide mechanism for managing resources across engines and executions #4281

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from

Conversation

YongGoose
Copy link
Contributor

@YongGoose YongGoose commented Jan 27, 2025

Resolves #2816

Overview

  • Designing a class to manage context at the platform level
  • Adding a session-level store to LauncherSession
  • Adding a request-level store to Launcher
  • Exposing the request-level store in EngineDiscoveryRequest and TestPlan ExecutionRequest
  • Utilizing the request-level store in ExtensionContext
  • Tests for Jupiter, Suite engine, EngineTestKit etc.

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


Definition of Done

@YongGoose
Copy link
Contributor Author

YongGoose commented Jan 27, 2025

What's the use case for this method? Test engines don't have access to the Launcher so we'll need to pass this store as part of LauncherDiscoveryRequest and ExecutionRequest, don't we?

I’ve drafted an initial version 02ef45b , but I think there’s still a lot of room for improvement.
If you could point out any mistakes I’ve made, I’ll make sure to fix them right away.

Even though I’ve been studying, I find it quite challenging.

PS. While resolving a git conflict, I accidentally included the commit history from the main branch, So i create a new PR 😁

@YongGoose
Copy link
Contributor Author

@marcphilipp

I think you must be very busy with the release of version 5.12 M1.
Please feel free to leave your comments whenever it’s convenient for you.

@YongGoose
Copy link
Contributor Author

YongGoose commented Feb 6, 2025

@marcphilipp

This PR is something I’ve personally been eager to work on for a long time.
However, I still don’t have a clear sense of how to proceed with it.

If this feature gets added to JUnit 5, it would be a huge learning opportunity for me and a great addition to my skill set.
That said, I don’t want to cause any fatigue for the JUnit team—especially you, Marc.

So if you feel that I’m not quite ready to take on this PR yet, please let me know, and I’ll close it.
Either way, I’ll keep looking for other issues and do my best to contribute! 🙂

I think I sounded a bit too weak.
After completing the other PR, I will make sure to implement this perfectly, no matter what.

But if you’re willing to let me see it through, I’ll do my best as always.
I’ll likely need more guidance than usual compared to other tasks.

* @return the store for session-level data
* @since 5.13
*/
Store getSessionLevelStore();
Copy link
Member

Choose a reason for hiding this comment

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

These also need an @API annotation:

@API(status = EXPERIMENTAL, since = "5.13")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 33f5fb8

* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.engine;
Copy link
Member

Choose a reason for hiding this comment

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

Can we move this to the same packaged as NamespacedHierarchicalStore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 9fd6fd8 !

@@ -59,6 +65,8 @@ class DefaultLauncher implements Launcher {
"PostDiscoveryFilter array must not contain null elements");
this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines,
unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners);
this.launcherSession = LauncherFactory.openSession(config);
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 not open a session here but pass in the sessionStore to the constructor.

@@ -43,6 +45,7 @@ public void close() {
private final LauncherInterceptor interceptor;
private final LauncherSessionListener listener;
private final DelegatingLauncher launcher;
private final NamespacedHierarchicalStore<Namespace> store;

DefaultLauncherSession(List<LauncherInterceptor> interceptors, Supplier<LauncherSessionListener> listenerSupplier,
Supplier<Launcher> launcherSupplier) {
Copy link
Member

Choose a reason for hiding this comment

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

This probably needs to be changed to the following:

Suggested change
Supplier<Launcher> launcherSupplier) {
Function<NamespacedHierarchicalStore<Namespace>, Launcher> launcherFactory) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm curious about the reason for changing from Supplier to Function.

Also, I'd like to understand the purpose of this variable.
More specifically, what kind of value is intended to be generated through the apply method?

Copy link
Member

Choose a reason for hiding this comment

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

This is to make creating the Launcher lazy so it can be intercepted (see else branch below).

Copy link
Member

Choose a reason for hiding this comment

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

Since it now needs to be passed the session-level store to its constructor, I think we'll need to pass that in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on the comments you left on the PR and the comments in the issue, I've made some progress on the implementation.

I've marked the parts where I'm still unsure about how to pass things with TODOs.

Thanks to your help, I'm starting to get a better understanding. I'll keep working hard. Thank you!

YongGoose and others added 4 commits February 12, 2025 19:15
…Namespace.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
…her/core/DefaultLauncherSession.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
Comment on lines 195 to 203
@Override
public Store getSessionLevelStore() {
return getStore(Namespace.create("session"));
}

@Override
public Store getRequestLevelStore() {
return getStore(Namespace.create("request"));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@marcphilipp

Is this the correct approach to creating session and request stores?

I understand that a Namespace is distinguished based on its name. However, I'm not sure if it's appropriate to create a Namespace by directly using the names of the session and request.

Copy link
Member

Choose a reason for hiding this comment

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

No, the namespace is an orthogonal concept here. Instead these methods should take a Namespace parameter. Sorry for omitting that in #2816 (comment)! They should return an adapted (they use a different Namespace type and Store interface) version of the NamespacedHierarchicalStore objects passed to the Jupiter engine. Please let me know if I should provide more details.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for omitting that in #2816 (comment)!

No, I think I should have asked earlier. 🥹

No, the namespace is an orthogonal concept here.

May I ask about the concept of Namespace?

Also, how does the Namespace in jupiter.api.extension differ from the one I created?
A more overall detailed explanation would be really helpful for my understanding. 🙂

Copy link
Contributor Author

@YongGoose YongGoose Feb 13, 2025

Choose a reason for hiding this comment

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

@marcphilipp

I studied on my own and found some answers.

Namespace is used to define the scope of data within a Store.
From what I understand, Jupiter uses a different Store, which is why it also requires a different type of Namespace. And as you suggested, I’ve modified the method to take Namespace as a parameter. Please review it when you have a chance. 5d2c2b8

However, I still don’t fully understand the NamespacedHierarchicalStore that is passed to the Jupiter engine.

Utilizing the request-level store in JupiterEngineExtensionContext

I expect this to be related to the following steps outlined in the ReadMe.

Copy link
Member

Choose a reason for hiding this comment

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

Namespace is used to define the scope of data within a Store.

That's correct. The original intention was that Jupiter extensions could store data in the Store without worrying about unintended side-effects (for example, another extension storing something else under the same key) while also allowing extensions to share state by using the same Namespace on purpose.

From what I understand, Jupiter uses a different Store, which is why it also requires a different type of Namespace.

That's correct, but Jupiter's implementation uses NamespacedHierarchicalStore internally.

Copy link
Member

@marcphilipp marcphilipp Feb 16, 2025

Choose a reason for hiding this comment

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

I pushed 4624808 and bac5250 to outline what I mean. ExecutionRequest is still using the wrong NamespacedHierarchicalStore, though, and I didn't add any tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ExecutionRequest is still using the wrong NamespacedHierarchicalStore, though, and I didn't add any tests.

I’ll take care of that task myself!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ExecutionRequest is still using the wrong NamespacedHierarchicalStore

First, I decided to modify the NamespacedHierarchicalStore in ExecutionRequest and then implement test cases.

ExecutionRequest is created by the EngineExecutionOrchestrator initialized by the Launcher. At this time, the store in ExecutionRequest serves as a request-level storage.

Accordingly, I implemented it so that ExecutionRequest receives the RequestLevelStore created in DefaultLauncher. 6b4ee03

@YongGoose YongGoose force-pushed the feature/2816 branch 2 times, most recently from e71438b to c9d5ac0 Compare February 13, 2025 10:40
@@ -63,6 +65,8 @@
@API(status = STABLE, since = "1.0")
public class LauncherFactory {

private static final NamespacedHierarchicalStore<Namespace> SESSION_STORE = new NamespacedHierarchicalStore<>(null);
Copy link
Contributor Author

@YongGoose YongGoose Feb 13, 2025

Choose a reason for hiding this comment

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

@marcphilipp

After much consideration, I’ve decided to focus on the Launcher part first.

To proceed, I need feedback on how LauncherFactory creates Launcher and LauncherSession.

My current approach is as follows:
LauncherFactory creates a session-level store and passes it to Launcher, setting it as the parent store for the request-level store.
And, LauncherSession receives the session-level store.

I’d like to know if this approach makes sense and whether the code is appropriate. I’d appreciate your feedback!

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 stores need to be closed, we need to attach them to the LauncherSession. I pushed 60f66f5 to illustrate what I mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was exactly the approach I had in mind!

Thank you so much. 🙇🏻

Copy link
Contributor Author

@YongGoose YongGoose left a comment

Choose a reason for hiding this comment

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

Removing the store from the discovery process sounds like a good idea.
As the next step, I will proceed with the following task.

  • Tests for Jupiter, Suite engine, EngineTestKit etc.

Thank you, Marc!

Comment on lines +92 to +95
@Override
public NamespacedHierarchicalStore<Namespace> getStore() {
return store;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
@Override
public NamespacedHierarchicalStore<Namespace> getStore() {
return store;
}
@Override
public NamespacedHierarchicalStore<Namespace> getSessionLevelStore() {
return sessionLevelStore;
}

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

I'm torn. On one hand that would make it more consistent, on the other hand, what else should it be? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The store in DefaultLauncherSession is only used for creating the Launcher.
So, setting it as store would be fine, but for consistency, I set it as sessionLevelStore.

Hmm... I can't think of any other approach at the moment. 😬
For now, shall we put this on hold and decide once we have a clearer answer?

Comment on lines +48 to +49
private final NamespacedHierarchicalStore<Namespace> store = new NamespacedHierarchicalStore<>(null,
closeAutoCloseables());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
private final NamespacedHierarchicalStore<Namespace> store = new NamespacedHierarchicalStore<>(null,
closeAutoCloseables());
private final NamespacedHierarchicalStore<Namespace> sessionLevelStore = new NamespacedHierarchicalStore<>(null,
closeAutoCloseables());

Copy link
Member

Choose a reason for hiding this comment

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

Ditto

@@ -47,4 +49,6 @@ public interface LauncherSession extends AutoCloseable {
@Override
void close();

NamespacedHierarchicalStore<Namespace> getStore();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
NamespacedHierarchicalStore<Namespace> getStore();
NamespacedHierarchicalStore<Namespace> getSessionLevelStore();

Copy link
Member

Choose a reason for hiding this comment

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

Ditto

@YongGoose
Copy link
Contributor Author

@marcphilipp

While working on the tests, I have a question.

As per your guidance, I am adding tests for JupiterEngine, SuiteEngine, and EngineKit. However, I am wondering if SuiteEngine requires furthermore testing.

Since the main focus of this PR is adding request-level and session-level stores, I am designing the tests around that.

The execute method in SuiteEngine utilizes the requestLevelStore from ExecutionRequest to run multiple tests. In my view, the core role of execute is to execute multiple tests, and this functionality is already being validated by existing tests.

Do you think any additional tests are needed for SuiteEngine?

PS. I truly appreciate how you not only explain things but also implement the code to make communication more efficient—it’s been incredibly helpful. 👍🏻
However, this time, I’d like to try implementing it on my own! 🙂

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.

Provide mechanism for managing resources across engines and executions
2 participants