Skip to content

Commit

Permalink
Add a cmdLineArgSeparator and unpackClientEar option to the AppClient…
Browse files Browse the repository at this point in the history
…ProtocolConfiguration

Add a clientAppArchive variable for use in the arquillian descriptor

Signed-off-by: Scott M Stark <starksm64@gmail.com>
  • Loading branch information
starksm64 committed Sep 13, 2024
1 parent 7ffba1d commit ef12777
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package tck.arquillian.protocol.appclient;

public record AppClientArchiveName(String name) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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> appClientArchiveName;

@Override
public Archive<?> generateDeployment(TestDeployment testDeployment, Collection<ProtocolArchiveProcessor> processors) {
Archive<?> archive = testDeployment.getApplicationArchive();
Expand All @@ -34,13 +57,13 @@ public Archive<?> generateDeployment(TestDeployment testDeployment, Collection<P
throw new RuntimeException("Failed to resolve protocol.jar. You either need a jakarta.tck.arquillian:arquillian-protocol-lib"+
" dependency in the runner pom.xml or a downloaded target/protocol/protocol.jar file");
}
ear.addAsLibrary(protocolJar);
ear.addAsLibrary(protocolJar, "arquillian-protocol-lib.jar");

AppClientProtocolConfiguration config = (AppClientProtocolConfiguration) testDeployment.getProtocolConfiguration();
String mainClass = extractAppMainClient(ear);
log.info("mainClass: " + mainClass);

// Write out the ear with the test dependencies for use by the appclient launcher
AppClientProtocolConfiguration config = (AppClientProtocolConfiguration) testDeployment.getProtocolConfiguration();
String extractDir = config.getClientEarDir();
if(extractDir == null) {
extractDir = "target/appclient";
Expand All @@ -58,10 +81,37 @@ public Archive<?> generateDeployment(TestDeployment testDeployment, Collection<P
exporter.exportTo(archiveOnDisk, true);
log.info("Exported test ear to: " + archiveOnDisk.getAbsolutePath());

if(config.isUnpackClientEar()) {
for (ArchivePath path : ear.getContent().keySet()) {
Node node = ear.get(path);
if (node.getAsset() instanceof ArchiveAsset) {
ArchiveAsset asset = (ArchiveAsset) node.getAsset();
File archiveFile = new File(appclient, path.get());
if(!archiveFile.getParentFile().exists()) {
archiveFile.getParentFile().mkdirs();
}
final ZipExporter zipExporter = asset.getArchive().as(ZipExporter.class);
zipExporter.exportTo(archiveFile, true);
log.info("Exported test ear content to: " + archiveFile.getAbsolutePath());
} else if(node.getAsset() instanceof FileAsset) {
FileAsset asset = (FileAsset) node.getAsset();
File file = new File(appclient, path.get());
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
try {
Files.copy(asset.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
log.info("Exported test ear content to: " + file.getAbsolutePath());
} catch (Exception e) {
throw new RuntimeException("Failed to export test ear content to: " + file.getAbsolutePath(), e);
}
}
}
}
return ear;
}

private static String extractAppMainClient(EnterpriseArchive ear) {
private String extractAppMainClient(EnterpriseArchive ear) {
String mainClass = null;
Map<ArchivePath, Node> contents = ear.getContent();
for (Node node : contents.values()) {
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -19,6 +30,9 @@ public class AppClientMethodExecutor implements ContainerMethodExecutor {
@Inject
@DeploymentScoped
private Instance<Deployment> deploymentInstance;
@Inject
@DeploymentScoped
private Instance<AppClientArchiveName> appClientArchiveName;

static enum MainStatus {
PASSED,
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand All @@ -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)}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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) {
Expand All @@ -136,15 +188,30 @@ 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;
if (clientEnvString != null) {
ArrayList<String> tmp = new ArrayList<String>();
// 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down

0 comments on commit ef12777

Please sign in to comment.