diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientArchiveName.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientArchiveName.java new file mode 100644 index 000000000..2f6d4ddaa --- /dev/null +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientArchiveName.java @@ -0,0 +1,4 @@ +package tck.arquillian.protocol.appclient; + +public record AppClientArchiveName(String name) { +} diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientCmd.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientCmd.java index 6528a195d..dd6fd8368 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientCmd.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientCmd.java @@ -104,11 +104,13 @@ public synchronized void quit() throws Exception { /** * Starts the app client in a new process and creates two threads to read the process output * and error streams. + * @param vehicleArchiveName - the name of the vehicle archive to pass to the app client + * @param clientAppArchive - the appclient archive * @param additionalArgs - additional arguments passed to the app client process. The CTS appclient will * pass in the name of the test to run using this. * @throws Exception - on failure */ - public void run(String vehicleArchiveName, String... additionalArgs) throws Exception { + public void run(String vehicleArchiveName, String clientAppArchive, String... additionalArgs) throws Exception { // Need to replace any property refs on command line File earDir = new File(clientEarDir); if(earDir.isAbsolute()) { @@ -125,6 +127,11 @@ public void run(String vehicleArchiveName, String... additionalArgs) throws Exce arg = arg.replaceAll("\\$\\{vehicleArchiveName}", vehicleArchiveName); cmdLine[n] = arg; } + if(arg.contains("${clientAppArchive}")) { + arg = arg.replaceAll("\\$\\{clientAppArchive}", clientAppArchive); + cmdLine[n] = arg; + } + } if (additionalArgs != null) { String[] newCmdLine = new String[cmdLine.length + additionalArgs.length]; @@ -196,6 +203,7 @@ private synchronized void outputLineReceived(String line) { private synchronized void errorLineReceived(String line) { LOGGER.info("[" + errThreadHame + "] " + line); + outputQueue.add(line); } private static String formatException(String msg, Throwable e) { diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientDeploymentPackager.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientDeploymentPackager.java index f2a1994bc..c90a60147 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientDeploymentPackager.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientDeploymentPackager.java @@ -1,26 +1,49 @@ +/* + * Copyright 2024 Red Hat, Inc., and individual contributors + * + * 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 + * + */ package tck.arquillian.protocol.appclient; import org.jboss.arquillian.container.test.spi.TestDeployment; import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentPackager; import org.jboss.arquillian.container.test.spi.client.deployment.ProtocolArchiveProcessor; +import org.jboss.arquillian.core.api.InstanceProducer; +import org.jboss.arquillian.core.api.annotation.ApplicationScoped; +import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ArchivePath; import org.jboss.shrinkwrap.api.Node; import org.jboss.shrinkwrap.api.asset.ArchiveAsset; import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.FileAsset; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.EnterpriseArchive; import tck.arquillian.protocol.common.ProtocolJarResolver; import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Map; import java.util.logging.Logger; +/** + * + */ public class AppClientDeploymentPackager implements DeploymentPackager { static Logger log = Logger.getLogger(AppClientDeploymentPackager.class.getName()); + @Inject + @ApplicationScoped + private InstanceProducer appClientArchiveName; + @Override public Archive generateDeployment(TestDeployment testDeployment, Collection processors) { Archive archive = testDeployment.getApplicationArchive(); @@ -34,13 +57,13 @@ public Archive generateDeployment(TestDeployment testDeployment, Collection

generateDeployment(TestDeployment testDeployment, Collection

contents = ear.getContent(); for (Node node : contents.values()) { @@ -78,6 +128,7 @@ private static String extractAppMainClient(EnterpriseArchive ear) { for (String line : lines) { if (line.startsWith("Main-Class:")) { mainClass = line.substring(11).trim(); + appClientArchiveName.set(new AppClientArchiveName(jar.getArchive().getName())); break; } } diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientMethodExecutor.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientMethodExecutor.java index a6c30a62b..ec9c0d92b 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientMethodExecutor.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientMethodExecutor.java @@ -1,9 +1,20 @@ +/* + * Copyright 2024 Red Hat, Inc., and individual contributors + * + * 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 + * + */ package tck.arquillian.protocol.appclient; import org.jboss.arquillian.container.spi.client.deployment.Deployment; import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; import org.jboss.arquillian.container.test.spi.ContainerMethodExecutor; import org.jboss.arquillian.core.api.Instance; +import org.jboss.arquillian.core.api.InstanceProducer; import org.jboss.arquillian.core.api.annotation.Inject; import org.jboss.arquillian.test.spi.TestMethodExecutor; import org.jboss.arquillian.test.spi.TestResult; @@ -19,6 +30,9 @@ public class AppClientMethodExecutor implements ContainerMethodExecutor { @Inject @DeploymentScoped private Instance deploymentInstance; + @Inject + @DeploymentScoped + private Instance appClientArchiveName; static enum MainStatus { PASSED, @@ -54,9 +68,10 @@ public TestResult invoke(TestMethodExecutor testMethodExecutor) { log.info("Running appClient for: " + testMethod); try { Deployment deployment = deploymentInstance.get(); + String appArchiveName = appClientArchiveName.get().name(); String vehicleArchiveName = TsTestPropsBuilder.vehicleArchiveName(deployment); String[] additionalAgrs = TsTestPropsBuilder.runArgs(config, deployment, testMethodExecutor); - appClient.run(vehicleArchiveName, additionalAgrs); + appClient.run(vehicleArchiveName, appArchiveName, additionalAgrs); } catch (Exception ex) { result = TestResult.failed(ex); return result; diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocol.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocol.java index 771d47939..8138dccdc 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocol.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocol.java @@ -1,3 +1,13 @@ +/* + * Copyright 2024 Red Hat, Inc., and individual contributors + * + * 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 + * + */ package tck.arquillian.protocol.appclient; import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; @@ -26,13 +36,18 @@ public ProtocolDescription getDescription() { @Override public DeploymentPackager getPackager() { - return new AppClientDeploymentPackager(); + AppClientDeploymentPackager packager = new AppClientDeploymentPackager(); + Injector injector = injectorInstance.get(); + injector.inject(packager); + + return packager; } @Override public ContainerMethodExecutor getExecutor(AppClientProtocolConfiguration protocolConfiguration, ProtocolMetaData metaData, CommandCallback callback) { + // Create the AppClientCmd and AppClientMethodExecutor instances and have arquillian inject the Deployment into the executor AppClientCmd clientCmd = new AppClientCmd(protocolConfiguration); AppClientMethodExecutor executor = new AppClientMethodExecutor(clientCmd, protocolConfiguration); Injector injector = injectorInstance.get(); diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolConfiguration.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolConfiguration.java index 467ea8107..e101026ab 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolConfiguration.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolConfiguration.java @@ -8,6 +8,10 @@ import java.io.IOException; import java.util.ArrayList; +/** + * Configuration for the AppClient protocol. This is used to configure the appclient process that will be launched to + * run the appclient main class for the test. + */ public class AppClientProtocolConfiguration implements ProtocolConfiguration, ProtocolCommonConfig { private boolean runClient = true; /** @@ -20,9 +24,14 @@ public class AppClientProtocolConfiguration implements ProtocolConfiguration, Pr */ private String clientEnvString; /** - * A comma separated string for the command line arguments to pass as the cmdarray to {@link Runtime#exec(String[], String[])} + * A ';' (by default) separated string for the command line arguments to pass as the cmdarray to + * {@link Runtime#exec(String[], String[])} */ private String clientCmdLineString; + /** + * The separator to use for splitting the clientCmdLineString + */ + private String cmdLineArgSeparator = ";"; /** * An optional directory string to use as the appclient process directory. This is passed as the dir arguemnt * to {@link Runtime#exec(String[], String[], File)} @@ -42,6 +51,7 @@ public class AppClientProtocolConfiguration implements ProtocolConfiguration, Pr private String tsSqlStmtFile; // harness.log.traceflag private boolean trace; + private boolean unpackClientEar = false; public boolean isAppClient() { return true; @@ -101,10 +111,31 @@ public String getClientCmdLineString() { return clientCmdLineString; } + /** + * Set the command line to use for launching the appclient. The individual arguments are separated by the cmdLineArgSeparator + * setting, which defaults to ';'. A long command line can be split across multiple lines in the arquillian.xml file because + * the parsed command line array elements are trimmed of leading and trailing whitespace. + * The command line should be filtered against the ts.jte file if it contains any property references. In addition + * to ts.jte property references, the command line can contain ${clientEarDir} which will be replaced with the + * #clientEarDir value. Any ${vehicleArchiveName} ref will be replaced with the vehicleArchiveName passed to the + * @param clientCmdLineString + */ public void setClientCmdLineString(String clientCmdLineString) { this.clientCmdLineString = clientCmdLineString; } + public String getCmdLineArgSeparator() { + return cmdLineArgSeparator; + } + + /** + * Set the separator to use for splitting the clientCmdLineString + * @param cmdLineArgSeparator + */ + public void setCmdLineArgSeparator(String cmdLineArgSeparator) { + this.cmdLineArgSeparator = cmdLineArgSeparator; + } + public String getClientDir() { return clientDir; } @@ -119,15 +150,36 @@ public void setClientEarDir(String clientEarDir) { this.clientEarDir = clientEarDir; } + public boolean isUnpackClientEar() { + return unpackClientEar; + } + + /** + * Set to true to unpack the client ear into the clientEarDir. The default is false. This is useful if the + * vendor appclient requires the ear to be exploded in order to access the appclient jar and bundled ear + * lib jars. + * @param unpackClientEar + */ + public void setUnpackClientEar(boolean unpackClientEar) { + this.unpackClientEar = unpackClientEar; + } + public long getClientTimeout() { return clientTimeout; } + /** + * Set the timeout in milliseconds for waiting for the appclient process to exit. The default is 60000 (1 minute). + * @param clientTimeout + */ public void setClientTimeout(long clientTimeout) { this.clientTimeout = clientTimeout; } - // Helper methods to turn the strings into the types used by Runtime#exec + + /** Helper methods to turn the strings into the types used by Runtime#exec + * @return a File object for the clientDir + */ public File clientDirAsFile() { File dir = null; if (clientDir != null) { @@ -136,8 +188,18 @@ public File clientDirAsFile() { return dir; } + /** + * Parse the clientCmdLineString into an array of strings using the cmdLineArgSeparator. This calls String#split on the + * clientCmdLineString and then trims each element of the resulting array. + * @return a command line array of strings for use with Runtime#exec. + */ public String[] clientCmdLineAsArray() { - return clientCmdLineString.trim().split(";"); + String[] cmdArray = clientCmdLineString.trim().split(cmdLineArgSeparator); + // Now trim each element + for (int i = 0; i < cmdArray.length; i++) { + cmdArray[i] = cmdArray[i].trim(); + } + return cmdArray; } public String[] clientEnvAsArray() { String[] envp = null; @@ -145,6 +207,11 @@ public String[] clientEnvAsArray() { ArrayList tmp = new ArrayList(); // Split on the env1=value1 ; separator envp = clientEnvString.trim().split(";"); + // Now trim each element + for (int i = 0; i < envp.length; i++) { + envp[i] = envp[i].trim(); + } + } return envp; } diff --git a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolExtension.java b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolExtension.java index 76da241c7..4950f4522 100644 --- a/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolExtension.java +++ b/arquillian/appclient/src/main/java/tck/arquillian/protocol/appclient/AppClientProtocolExtension.java @@ -1,8 +1,21 @@ +/* + * Copyright 2024 Red Hat, Inc., and individual contributors + * + * 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 + * + */ package tck.arquillian.protocol.appclient; import org.jboss.arquillian.container.test.spi.client.protocol.Protocol; import org.jboss.arquillian.core.spi.LoadableExtension; +/** + * Arquillian extension for the AppClient protocol. + */ public class AppClientProtocolExtension implements LoadableExtension { @Override diff --git a/arquillian/common/src/main/java/tck/arquillian/protocol/common/TsTestPropsBuilder.java b/arquillian/common/src/main/java/tck/arquillian/protocol/common/TsTestPropsBuilder.java index 530e48875..df27e9c5d 100644 --- a/arquillian/common/src/main/java/tck/arquillian/protocol/common/TsTestPropsBuilder.java +++ b/arquillian/common/src/main/java/tck/arquillian/protocol/common/TsTestPropsBuilder.java @@ -203,7 +203,7 @@ public static String[] runArgs(ProtocolCommonConfig config, Deployment deploymen // test props are needed by EETest.run "-p", testProps.toFile().getAbsolutePath(), "-ap", tssqlStmt != null ? tssqlStmt.toFile().getAbsolutePath() : "/dev/null", - "classname", testMethodExecutor.getMethod().getDeclaringClass().getName(), + "-classname", testMethodExecutor.getMethod().getDeclaringClass().getName(), "-t", testMethodName, "-vehicle", vehicle, };