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

org.jboss.arquillian.test.spi.ExceptionProxy does not produce meaningful stack trace when exception class is missing on client #641

Open
jamezp opened this issue Nov 7, 2024 · 6 comments · May be fixed by #656

Comments

@jamezp
Copy link
Collaborator

jamezp commented Nov 7, 2024

Issue Overview

When attempting to use Assertions.assertInstanceOf() I received a very cryptic error. This was for an in-container test.

Assertions.assertInstanceOf(ResteasyClient.class, client);

Error from Arqullian:

[ERROR] org.jboss.resteasy.test.client.other.ClientHttpEngineTest.testInjectedClient -- Time elapsed: 188.3 s <<< ERROR!
org.jboss.arquillian.test.spi.ArquillianProxyException: org.jboss.arquillian.junit5.IdentifiedTestException : null [Proxied because : Original exception caused: class java.lang.ClassNotFoundException: org.jboss.weld.generated.proxies.ws.rs.client.Client$Configurable$AutoCloseable$2078960113$Proxy$_$$_WeldClientProxy]
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.junit5.container.JUnitJupiterTestRunner$ArquillianTestMethodExecutionListener.getTestResult(JUnitJupiterTestRunner.java:101)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.junit5.container.JUnitJupiterTestRunner$ArquillianTestMethodExecutionListener.access$100(JUnitJupiterTestRunner.java:69)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.junit5.container.JUnitJupiterTestRunner.execute(JUnitJupiterTestRunner.java:58)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.protocol.servlet5.runner.ServletTestRunner.executeTest(ServletTestRunner.java:139)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.protocol.servlet5.runner.ServletTestRunner.execute(ServletTestRunner.java:117)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.protocol.servlet5.runner.ServletTestRunner.doGet(ServletTestRunner.java:86)
    at [email protected]//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:633)
    at [email protected]//jakarta.servlet.http.HttpServlet.service(HttpServlet.java:723)
    at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at [email protected]//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.lambda$handleRequest$1(ElytronRunAsHandler.java:68)
    at [email protected]//org.wildfly.security.auth.server.FlexibleIdentityAssociation.runAsFunctionEx(FlexibleIdentityAssociation.java:103)
    at [email protected]//org.wildfly.security.auth.server.Scoped.runAsFunctionEx(Scoped.java:161)
    at [email protected]//org.wildfly.security.auth.server.Scoped.runAs(Scoped.java:73)
    at [email protected]//org.wildfly.elytron.web.undertow.server.ElytronRunAsHandler.handleRequest(ElytronRunAsHandler.java:67)
    at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
    at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
    at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at org.wildfly.security.elytron-web.undertow-server-servlet@4.1.0.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:44)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:51)
    at [email protected]//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:276)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:132)
    at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1421)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:256)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:101)
    at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:395)
    at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:861)
    at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1348)
    at [email protected]//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
    at java.base/java.lang.Thread.run(Thread.java:840)
Expected Behaviour

The true assertion should have been printed which I found in debugging was:

org.opentest4j.AssertionFailedError: Unexpected type, expected: <org.jboss.resteasy.client.jaxrs.ResteasyClient> but was: <org.jboss.weld.generated.proxies.ws.rs.client.Client$Configurable$AutoCloseable$2078960113$Proxy$_$$_WeldClientProxy>
    at deployment.ClientHttpEngineTest.war//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
    at deployment.ClientHttpEngineTest.war//org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
    at deployment.ClientHttpEngineTest.war//org.junit.jupiter.api.AssertInstanceOf.assertInstanceOf(AssertInstanceOf.java:49)
    at deployment.ClientHttpEngineTest.war//org.junit.jupiter.api.AssertInstanceOf.assertInstanceOf(AssertInstanceOf.java:31)
    at deployment.ClientHttpEngineTest.war//org.junit.jupiter.api.Assertions.assertInstanceOf(Assertions.java:3614)
    at deployment.ClientHttpEngineTest.war//org.jboss.resteasy.test.client.other.ClientHttpEngineTest.testInjectedClient(ClientHttpEngineTest.java:68)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at deployment.ClientHttpEngineTest.war//org.jboss.arquillian.junit5.ArquillianExtension.interceptTestMethod(ArquillianExtension.java:87)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Current Behaviour

The error above is printed which doesn't really explain what is happening.

Steps To Reproduce

I've not attempted to create a reproducer, but something simple like this would work with WildFly Arquillian.

package org.jboss.resteasy.test.client.other;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.client.Client;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(ArquillianExtension.class)
@ApplicationScoped
public class ClientHttpEngineTest {

    @Inject
    private Client client;

    @Deployment
    public static WebArchive deployment() {
        return ShrinkWrap.create(WebArchive.class, ClientHttpEngineTest.class.getSimpleName() + ".war")
                .addClass(ClientHttpEngineTest.class)
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @Test
    public void testInjectedClient() throws Exception {
        Assertions.assertInstanceOf(ResteasyClient.class, client);
    }
}

Note the test will fail because the injected jakarta.ws.rs.client.Client is a Weld proxy and not an instance of org.jboss.resteasy.client.jaxrs.ResteasyClient. However, I had to debug Arquillian to figure that out.

Additional Information
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /home/jperkins/apps/maven
Java version: 17.0.13, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.13.0.11-1.fc41.x86_64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.11.5-300.fc41.x86_64", arch: "amd64", family: "unix"
@starksm64
Copy link
Member

starksm64 commented Dec 14, 2024

I just ran into a similar problematic error for a weld failure. The issue is that the weld classes involved in the exception on the server need to be on the client classpath in order for the org.jboss.arquillian.test.spi.ExceptionProxy used by org.jboss.arquillian.test.spi.TestResult to work correctly.

We really should just be sending back the server side stack trace serialized as a string rather than trying to serialize exceptions that are often not going to be in a test client setup.

@starksm64 starksm64 changed the title Misleading error when using Assertions.assertInstanceOf, or possibly a bug in the servlet protocol org.jboss.arquillian.test.spi.ExceptionProxy does not produce meaningful stack trace when exception class is missing on client Dec 17, 2024
@starksm64
Copy link
Member

So there might be a couple of levels of issues here. One is that when an ArquillianProxyException is thrown, there was a failure to deserialize the server side exception. The ExceptionProxy does have the server side StackTraceElement[], but there is not a good way to attach it as a "Caused by: ..." trace as one would expect without having a cause, but this was lost during deserialization.

I have a non-remote container testcase in the test/spi module showing this ClassNotFoundException issue, and the trace it has corresponds to the location where the exception was thrown. In this test I do see the correct stacktrace, but the message is a little obfuscated. Your example has another layer of indirection as the ArquillianProxyException is referring to a org.jboss.arquillian.junit5.IdentifiedTestException as the exception that was the problem. This exception does not set a cause from its collectedExceptions map, so it is hard to say if this is just an exception wrapping issue or there was another problem in handling the ClassNotFoundException that is causing problems.

The ExceptionProxy is trying to handle cases like the exception thrown in the server container is not even serializable. I'm thinking of trying to serialize a map of the original cause class hierarchy as just the class names, and during deserialization, the first exception class in the hierarchy found in the client side would be created with the message and stacktrace from the server.

We will have to create additional tests for the container remote execution to validate this is getting passed through correctly. Just glancing at the current tests for the junit5 container it is not clear it is really validating a remote execution scenario.

@starksm64
Copy link
Member

starksm64 commented Dec 17, 2024

Doing that in a backward portable manner is complicate by the use of Externalizable because we cannot rely on the type evolution features of the default Serializable framework. I don't know how many years(decades) it has been since I actually dealt with serialization versioning!

@jamezp
Copy link
Collaborator Author

jamezp commented Dec 17, 2024

Yeah. The only solution I can think of would be to treat the stack trace from the server as a java.lang.String, but that likely isn't ideal.

@starksm64
Copy link
Member

It turns out the stacktrace is not the problem. The problem is trying to carry the original cause around that in many cases will not even exist on the client. Rather there is some common parent exception from the api standards that is what matters.

@starksm64
Copy link
Member

starksm64 commented Dec 17, 2024

#656 is a first step, but I think we should all but eliminate the use of the ArquillianProxyException as you can always restore some exception in the type hierarchy with a message. Maybe the only time it would be thrown is if a checked exception tests on a test method and only Throwable was able to be created.

The current ExceptionProxyTestCase is testing some questionable stuff regarding causes that are seen in an exception hierarchy that could no be fully serialized/deserialized.

I'll have to test this will a real remote test case as it seems like there still could be a problem with the junit container remote exception handling.

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 a pull request may close this issue.

2 participants