Skip to content

improve class loading for tests#4051

Merged
filiphr merged 3 commits into
mapstruct:mainfrom
hduelme:improve-class-loading-for-tests
May 14, 2026
Merged

improve class loading for tests#4051
filiphr merged 3 commits into
mapstruct:mainfrom
hduelme:improve-class-loading-for-tests

Conversation

@hduelme
Copy link
Copy Markdown
Contributor

@hduelme hduelme commented May 10, 2026

I improved class loading for tests by replacing LauncherDiscoveryListener with CompilerLauncherInterceptor (introduced in junit-team/junit-framework#3091). This change reduces overhead during test discovery and execution and also enables proper lifecycle handling, including correct closing of ClassLoader instances.

I also attempted to implement #3961, but was unable to get it working. The InvocationInterceptor only allows interception at test execution time, which is too late in this case: by then, test classes have already been loaded. This leads to IllegalAccessError issues when multiple ClassLoader instances attempt to load the same package, which is not permitted.

Copy link
Copy Markdown
Member

@filiphr filiphr left a comment

Choose a reason for hiding this comment

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

This looks really really good @hduelme. From what I can see it halves the time. From around 12 minutes to around 6 minutes for all the tests.

I need to find some time to have a quick look around how we want to store the private fields and to understand hot the CompilerLauncherInterceptor is instantiated.

If I look at the example from the docs:

https://docs.junit.org/6.0.3/advanced-topics/launcher-api.html#launcher-interceptors-custom

the custom classloader is instantiated in the constructor, and the original one is reset in the intercept. However, it could be that ours is needed later and you did all the heavy lifting checks, but I want to play with it a bit.

@hduelme
Copy link
Copy Markdown
Contributor Author

hduelme commented May 11, 2026

@filiphr
My implementation took inspiration from an old PR of quarkus 34681.

I did some experiments myself and restoring the ClassLoader to originalClassLoader in the finally block slows down the tests noticeably.
Ensuring that only a single instance of ModifiableURLClassLoader is created looks necessary for me. I moved the instantiation of the custom ClassLoader into the constructor.

Copy link
Copy Markdown
Member

@filiphr filiphr left a comment

Choose a reason for hiding this comment

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

I just tried this out locally, it's almost 3x speedup locally, that's just amazing @hduelme. I've left a comment, I think that we are losing the correct originalClassLoader.

Comment on lines +31 to +35
orginalClassLoader = Thread.currentThread().getContextClassLoader();
if ( !orginalClassLoader.getClass().isAssignableFrom( ModifiableURLClassLoader.class ) ) {
Thread.currentThread().setContextClassLoader( newClassLoader );
}
return invocation.proceed();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks a bit weird. Looking at this we end up with the originalClassLoader becoming an instance of the ModifiableURLClassLoader. Shouldn't we only set the originalClassLoader if it is not assignable from ModifiableURLClassLoader? i.e. if we actually change the context class loader?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. That was not what should happen. I changed the implementation as you suggested.

@hduelme
Copy link
Copy Markdown
Contributor Author

hduelme commented May 14, 2026

@filiphr Good catch. That was not what should happen. That's should be fixed now

@filiphr filiphr merged commit c488168 into mapstruct:main May 14, 2026
9 checks passed
@filiphr
Copy link
Copy Markdown
Member

filiphr commented May 14, 2026

Amazing. Thanks @hduelme.

By the way, when you can, can you reach out to me by email? Your commits don't have an email I can use to reach out, that's why I am asking you to reach out to me 😀

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.

2 participants