diff --git a/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableContainerConfiguration.java b/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableContainerConfiguration.java index 7dad8d8f..8c317ff4 100644 --- a/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableContainerConfiguration.java +++ b/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableContainerConfiguration.java @@ -31,6 +31,11 @@ public class BootableContainerConfiguration extends CommonManagedContainerConfig private String javaVmArguments = System.getProperty("jboss.options"); + private boolean debug = getBooleanProperty("wildfly.debug", false); + private int debugPort = Integer.parseInt(System.getProperty("wildfly.debug.port", "8787")); + + private boolean debugSuspend = getBooleanProperty("wildfly.debug.suspend", true); + private String jbossArguments; private boolean enableAssertions = true; @@ -43,6 +48,30 @@ public void setJavaVmArguments(String javaVmArguments) { this.javaVmArguments = javaVmArguments; } + public boolean isDebug() { + return debug; + } + + public void setDebug(final boolean debug) { + this.debug = debug; + } + + public int getDebugPort() { + return debugPort; + } + + public void setDebugPort(final int debugPort) { + this.debugPort = debugPort; + } + + public boolean isDebugSuspend() { + return debugSuspend; + } + + public void setDebugSuspend(final boolean debugSuspend) { + this.debugSuspend = debugSuspend; + } + public String getJbossArguments() { return jbossArguments; } @@ -80,4 +109,12 @@ public String getInstallDir() { public void setInstallDir(String installDir) { this.installDir = installDir; } + + private static boolean getBooleanProperty(final String key, final boolean dft) { + final String value = System.getProperty(key); + if (value != null) { + return value.isBlank() || Boolean.parseBoolean(value); + } + return dft; + } } diff --git a/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableDeployableContainer.java b/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableDeployableContainer.java index 7d719879..9ab969cb 100644 --- a/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableDeployableContainer.java +++ b/container-bootable/src/main/java/org/jboss/as/arquillian/container/bootable/BootableDeployableContainer.java @@ -77,6 +77,11 @@ protected CommandBuilder createCommandBuilder(final BootableContainerConfigurati commandBuilder.addServerArguments(ParameterUtils.splitParams(jbossArguments)); } + // Check if we should enable debug + if (config.isDebug()) { + commandBuilder.setDebug(config.isDebugSuspend(), config.getDebugPort()); + } + return commandBuilder; } diff --git a/container-bootable/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java b/container-bootable/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java new file mode 100644 index 00000000..d80b693e --- /dev/null +++ b/container-bootable/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java @@ -0,0 +1,121 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.as.arquillian.container.managed.manual; + +import java.io.IOException; +import java.util.List; + +import org.jboss.arquillian.container.test.api.ContainerController; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.as.arquillian.container.ManagementClient; +import org.jboss.as.controller.client.helpers.Operations; +import org.jboss.dmr.ModelNode; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import com.sun.jdi.Bootstrap; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; + +/** + * @author James R. Perkins + */ +@Category(ManualMode.class) +@RunWith(Arquillian.class) +@RunAsClient +public class DebugManualModeTestCase { + private static final String DEBUG_SUSPEND_CONTAINER_ID = "debug-config"; + + @ArquillianResource + private static ContainerController controller; + + @ArquillianResource + @TargetsContainer(DEBUG_SUSPEND_CONTAINER_ID) + private ManagementClient debugSuspendClient; + + @Deployment(managed = false, name = "dep1") + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class) + // Required for JUnit when running in ARQ + .addClass(ManualMode.class); + } + + @After + public void shutdown() { + if (controller.isStarted(DEBUG_SUSPEND_CONTAINER_ID)) { + controller.stop(DEBUG_SUSPEND_CONTAINER_ID); + } + } + + @Test + public void debugConfig() throws Exception { + controller.start(DEBUG_SUSPEND_CONTAINER_ID); + + // Attach a debugger + final VirtualMachine vm = attachDebugger(); + try { + Assert.assertFalse("VM should be able to see all threads: " + vm, vm.allThreads().isEmpty()); + // Check the server-state + final ModelNode address = new ModelNode().setEmptyList(); + final ModelNode op = Operations.createReadAttributeOperation(address, "server-state"); + final ModelNode result = executeOperation(debugSuspendClient, op); + Assert.assertEquals("running", result.asString()); + } finally { + vm.dispose(); + } + } + + private static ModelNode executeOperation(final ManagementClient client, final ModelNode op) throws IOException { + final ModelNode result = client.getControllerClient().execute(op); + if (!Operations.isSuccessfulOutcome(result)) { + Assert.fail(Operations.getFailureDescription(result).asString()); + } + return Operations.readResult(result); + } + + private static VirtualMachine attachDebugger() throws IllegalConnectorArgumentsException, IOException { + final var manager = Bootstrap.virtualMachineManager(); + final AttachingConnector connector = findSocket(manager.attachingConnectors()); + Assert.assertNotNull("Failed to find socket connector", connector); + final var defaultArguments = connector.defaultArguments(); + defaultArguments.get("port").setValue(System.getProperty("test.debug.port", "5005")); + return connector.attach(defaultArguments); + } + + private static AttachingConnector findSocket(final List connectors) { + // Attempt to find the socket connector and configure it + for (AttachingConnector connector : connectors) { + if (connector.defaultArguments().containsKey("port")) { + return connector; + } + } + return null; + } +} diff --git a/container-bootable/src/test/resources/manual-arquillian.xml b/container-bootable/src/test/resources/manual-arquillian.xml index d92654b2..6d445158 100644 --- a/container-bootable/src/test/resources/manual-arquillian.xml +++ b/container-bootable/src/test/resources/manual-arquillian.xml @@ -38,5 +38,17 @@ ${jvm.args} + + + + ${install.dir} + ${bootable.jar} + false + ${jvm.args} + true + ${test.debug.port:5005} + false + + diff --git a/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedContainerConfiguration.java b/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedContainerConfiguration.java index 45cf3d0b..9ba51955 100644 --- a/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedContainerConfiguration.java +++ b/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedContainerConfiguration.java @@ -37,6 +37,11 @@ public class ManagedContainerConfiguration extends DistributionContainerConfigur private String moduleOptions; + private boolean debug = getBooleanProperty("wildfly.debug", false); + private int debugPort = Integer.parseInt(System.getProperty("wildfly.debug.port", "8787")); + + private boolean debugSuspend = getBooleanProperty("wildfly.debug.suspend", true); + private String serverConfig = System.getProperty("jboss.server.config.file.name"); private String readOnlyServerConfig = System.getProperty("jboss.server.config.file.name.readonly"); @@ -109,6 +114,30 @@ public void setModuleOptions(String moduleOptions) { this.moduleOptions = moduleOptions; } + public boolean isDebug() { + return debug; + } + + public void setDebug(final boolean debug) { + this.debug = debug; + } + + public int getDebugPort() { + return debugPort; + } + + public void setDebugPort(final int debugPort) { + this.debugPort = debugPort; + } + + public boolean isDebugSuspend() { + return debugSuspend; + } + + public void setDebugSuspend(final boolean debugSuspend) { + this.debugSuspend = debugSuspend; + } + /** * Get the server configuration file name. Equivalent to [-server-config=...] on the command line. */ @@ -244,4 +273,12 @@ private static boolean isWindows() { final String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ROOT); return os.contains("windows"); } + + private static boolean getBooleanProperty(final String key, final boolean dft) { + final String value = System.getProperty(key); + if (value != null) { + return value.isBlank() || Boolean.parseBoolean(value); + } + return dft; + } } diff --git a/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedDeployableContainer.java b/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedDeployableContainer.java index e9319f5b..d5fbf179 100644 --- a/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedDeployableContainer.java +++ b/container-managed/src/main/java/org/jboss/as/arquillian/container/managed/ManagedDeployableContainer.java @@ -126,6 +126,11 @@ protected CommandBuilder createCommandBuilder(ManagedContainerConfiguration conf } } + // Check if we should enable debug + if (config.isDebug()) { + commandBuilder.setDebug(config.isDebugSuspend(), config.getDebugPort()); + } + // Previous versions of arquillian set the jboss.home.dir property in the JVM properties. // Some tests may rely on this behavior, but could be considered to be removed as all the scripts add this // property after the modules path (-mp) has been defined. The command builder will set the property after diff --git a/container-managed/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java b/container-managed/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java new file mode 100644 index 00000000..d80b693e --- /dev/null +++ b/container-managed/src/test/java/org/jboss/as/arquillian/container/managed/manual/DebugManualModeTestCase.java @@ -0,0 +1,121 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.as.arquillian.container.managed.manual; + +import java.io.IOException; +import java.util.List; + +import org.jboss.arquillian.container.test.api.ContainerController; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.as.arquillian.container.ManagementClient; +import org.jboss.as.controller.client.helpers.Operations; +import org.jboss.dmr.ModelNode; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import com.sun.jdi.Bootstrap; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; + +/** + * @author James R. Perkins + */ +@Category(ManualMode.class) +@RunWith(Arquillian.class) +@RunAsClient +public class DebugManualModeTestCase { + private static final String DEBUG_SUSPEND_CONTAINER_ID = "debug-config"; + + @ArquillianResource + private static ContainerController controller; + + @ArquillianResource + @TargetsContainer(DEBUG_SUSPEND_CONTAINER_ID) + private ManagementClient debugSuspendClient; + + @Deployment(managed = false, name = "dep1") + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class) + // Required for JUnit when running in ARQ + .addClass(ManualMode.class); + } + + @After + public void shutdown() { + if (controller.isStarted(DEBUG_SUSPEND_CONTAINER_ID)) { + controller.stop(DEBUG_SUSPEND_CONTAINER_ID); + } + } + + @Test + public void debugConfig() throws Exception { + controller.start(DEBUG_SUSPEND_CONTAINER_ID); + + // Attach a debugger + final VirtualMachine vm = attachDebugger(); + try { + Assert.assertFalse("VM should be able to see all threads: " + vm, vm.allThreads().isEmpty()); + // Check the server-state + final ModelNode address = new ModelNode().setEmptyList(); + final ModelNode op = Operations.createReadAttributeOperation(address, "server-state"); + final ModelNode result = executeOperation(debugSuspendClient, op); + Assert.assertEquals("running", result.asString()); + } finally { + vm.dispose(); + } + } + + private static ModelNode executeOperation(final ManagementClient client, final ModelNode op) throws IOException { + final ModelNode result = client.getControllerClient().execute(op); + if (!Operations.isSuccessfulOutcome(result)) { + Assert.fail(Operations.getFailureDescription(result).asString()); + } + return Operations.readResult(result); + } + + private static VirtualMachine attachDebugger() throws IllegalConnectorArgumentsException, IOException { + final var manager = Bootstrap.virtualMachineManager(); + final AttachingConnector connector = findSocket(manager.attachingConnectors()); + Assert.assertNotNull("Failed to find socket connector", connector); + final var defaultArguments = connector.defaultArguments(); + defaultArguments.get("port").setValue(System.getProperty("test.debug.port", "5005")); + return connector.attach(defaultArguments); + } + + private static AttachingConnector findSocket(final List connectors) { + // Attempt to find the socket connector and configure it + for (AttachingConnector connector : connectors) { + if (connector.defaultArguments().containsKey("port")) { + return connector; + } + } + return null; + } +} diff --git a/container-managed/src/test/resources/manual-arquillian.xml b/container-managed/src/test/resources/manual-arquillian.xml index c33034b4..c0d3b31a 100644 --- a/container-managed/src/test/resources/manual-arquillian.xml +++ b/container-managed/src/test/resources/manual-arquillian.xml @@ -57,5 +57,16 @@ ${wildfly.standalone.config} + + + + ${jboss.home} + false + ${jvm.args} + true + ${test.debug.port:5005} + false + + diff --git a/pom.xml b/pom.xml index 3569d2de..d0b6bf34 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ ${default.jvm.args} 8787 + 5005 3.0.0-M6