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

Test using LauncherInterceptor fails when executed with Gradle #3746

Closed
1 task
cdsap opened this issue Mar 25, 2024 · 1 comment
Closed
1 task

Test using LauncherInterceptor fails when executed with Gradle #3746

cdsap opened this issue Mar 25, 2024 · 1 comment

Comments

@cdsap
Copy link

cdsap commented Mar 25, 2024

In a test using a simple launcher interceptor, test passes if it is executed with the IDE Junit configuration but fails with the IDE Gradle configuration or ./gradlew test command-line execution. I'm not sure if this issue falls on Junit or Gradle BT. Please, let me know if I should close this issue and create a new one with Gradle.

Having the following LauncherInterceptor:

public class CustomLauncherInterceptor implements LauncherInterceptor {

    private final URLClassLoader customClassLoader;

    public CustomLauncherInterceptor() throws Exception {
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        List<URL> urls = new ArrayList<>();
        urls.add(this.getClass().getResource("/"));
        customClassLoader = new ExampleClassLoader(urls.toArray(URL[]::new), parent);
    }

    @Override
    public <T> T intercept(Invocation<T> invocation) {
        Thread currentThread = Thread.currentThread();
        ClassLoader originalClassLoader = currentThread.getContextClassLoader();
        currentThread.setContextClassLoader(customClassLoader);
        try {
            return invocation.proceed();
        }
        finally {
            currentThread.setContextClassLoader(originalClassLoader);
        }
    }
...

where the ExampleClassLoader is:

public class ExampleClassLoader extends URLClassLoader {
    public ExampleClassLoader(URL[] urls, ClassLoader classLoader) {
        super(urls, classLoader);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, true);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        var ex = findLoadedClass(name);
        if (ex != null) {
            return ex;
        }
        var res = getResource(name.replace(".", "/") + ".class");
        if (res != null && res.getProtocol().equals("file")) {
            try (var in = res.openStream()) {
                var data = in.readAllBytes();
                return defineClass(name, data, 0, data.length);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        return super.loadClass(name, resolve);
    }
}

the expected results for the test:

    @Test
    public void classLoaderTest() {
        String loader = this.getClass().getClassLoader().getClass().getName();
        assertThat(loader).isEqualTo("ExampleClassLoader");
    }

is passing when executing from the IDE and gradle command:
Screenshot 2024-03-25 at 9 56 01 AM

However, the result with Gradle execution is:

Expected :"ExampleClassLoader"
Actual   :"jdk.internal.loader.ClassLoaders$AppClassLoader"

(interceptors has been enabled in the platform properties and CustomLauncherInterceptor registered in the services file)

Steps to reproduce

  1. Checkout repository https://github.com/cdsap/InterceptorReproducerIssue
  2. Execute CustomClassLoaderTest with Junit Configuration in the IDE. Test passes.
  3. Execute CustomClassLoaderTest with Gradle configuration or command-line gradle command. Test fails.

Context

Deliverables

  • ...
@cdsap cdsap changed the title Test using LauncherInterceptor fails when is executed with Gradle Test using LauncherInterceptor fails when executed with Gradle Mar 25, 2024
@marcphilipp
Copy link
Member

marcphilipp commented Mar 28, 2024

Hi Iñaki! 🙂 👋

That's caused by Gradle calling selectClass(Class) after loading the class with the default class loader early to perform some checks.

If Gradle would call selectClass(String), it would work. Switching those checks to read the bytecode instead of using reflection would avoid initializing the class potentially multiple times. However, using ASM for that should be avoided since newer JDK require upgrading. The Class-File API would avoid that in the future. I'll leave the decision whether to open an issue in the Gradle repo up to you.

As a workaround, you can replace the ClassLoader in the constructor of CustomLauncherInterceptor rather than in intercept().

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

No branches or pull requests

2 participants