Skip to content

Commit

Permalink
Merge pull request #463 from jamezp/WFARQ-173
Browse files Browse the repository at this point in the history
[WFARQ-173] Invoke test methods on the correct arquillian configuration
  • Loading branch information
jamezp authored Jun 6, 2024
2 parents 412d81c + d5805f1 commit 5a5cd00
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public Archive<?> generateDeployment(TestDeployment testDeployment,
}
}
addModulesManifestDependencies(appArchive);
TestDescription.addTestDescription(testDeployment);
archiveHolder.addPreparedDeployment(testDeployment.getDeploymentName());
return appArchive;
}
Expand All @@ -135,7 +136,7 @@ private JavaArchive generateArquillianServiceArchive(Collection<Archive<?>> auxA
archive.addPackage(AbstractJMXProtocol.class.getPackage());
// add the classes required for server setup
archive.addClasses(ServerSetup.class, ServerSetupTask.class, ManagementClient.class, Authentication.class,
NetworkUtils.class);
NetworkUtils.class, TestDescription.class);

final Set<ModuleIdentifier> archiveDependencies = new LinkedHashSet<ModuleIdentifier>();
archiveDependencies.add(ModuleIdentifier.create("org.jboss.as.jmx"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* 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.protocol.jmx;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.spi.TestDeployment;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Filter;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.vfs.VirtualFile;

/**
* A simple definition describing a test deployment.
*
* @author <a href="mailto:[email protected]">James R. Perkins</a>
*/
public class TestDescription {

private static final String PATH = "/META-INF/test-description.properties";
private static final String TARGET_CONTAINER = "org.jboss.as.arquillian.protocol.jmx.target.container";
private static final String ARQ_DEPLOYMENT_NAME = "org.jboss.as.arquillian.protocol.jmx.arq.deployment.name";
private static final String DEPLOYMENT_NAME = "org.jboss.as.arquillian.protocol.jmx.deployment.name";
private static final Filter<ArchivePath> ROOT_FILTER = (p) -> p.getParent() == null || p.getParent().get().equals("/");

private final Properties properties;

private TestDescription(final Properties properties) {
this.properties = properties;
}

/**
* Gets the test description from the deployment.
*
* @param deploymentUnit the deployment unit
*
* @return the test description from the deployment
*/
public static TestDescription from(final DeploymentUnit deploymentUnit) {
// Get the properties
final ResourceRoot resourceRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
final VirtualFile testDescription = resourceRoot.getRoot().getChild(TestDescription.PATH);
final Properties properties = new Properties();
if (testDescription != null && testDescription.exists()) {
try (InputStream in = testDescription.openStream()) {
properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return new TestDescription(properties);
}

/**
* Creates a test description based on the test deployment and attaches the description to the deployment for later
* usage. For EAR's this attaches the configuration to each module in the EAR.
*
* @param testDeployment the test deployment to gather information from
*/
public static void addTestDescription(final TestDeployment testDeployment) {
String targetContainer = null;
String arqDeploymentName = null;
if (testDeployment.getTargetDescription() != null) {
targetContainer = testDeployment.getTargetDescription().getName();
}
if (testDeployment.getDeploymentName() != null) {
arqDeploymentName = testDeployment.getDeploymentName();
}
final Archive<?> archive = testDeployment.getApplicationArchive();
if (archive instanceof EnterpriseArchive) {
// We need to update EAR's modules separately
final EnterpriseArchive ear = (EnterpriseArchive) archive;
final Map<ArchivePath, Node> modules = ear
.getContent(ROOT_FILTER);
for (Node module : modules.values()) {
if (module.getAsset() instanceof ArchiveAsset) {
final Archive<?> moduleArchive = ((ArchiveAsset) module.getAsset()).getArchive();
addTestDescription(moduleArchive, targetContainer, arqDeploymentName);
}
}
}
// Always add a description for the current archive
addTestDescription(archive, targetContainer, arqDeploymentName);
}

/**
* The container the test and deployment target.
*
* @return the optional name of the target container for the test
*/
public Optional<String> targetContainer() {
return Optional.ofNullable(properties.getProperty(TARGET_CONTAINER));
}

/**
* The deployments name. This will be the name of the deployed archive.
*
* @return the deployments name
*/
public String deploymentName() {
return properties.getProperty(DEPLOYMENT_NAME);
}

/**
* The name of the deployment relevant to Arquillian. This is the value from {@link Deployment#name()}
*
* @return the optional name of the arquillian deployment
*/
public Optional<String> arquillianDeploymentName() {
return Optional.ofNullable(properties.getProperty(ARQ_DEPLOYMENT_NAME));
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TestDescription)) {
return false;
}

final TestDescription other = (TestDescription) o;
return Objects.equals(targetContainer(), other.targetContainer())
&& Objects.equals(deploymentName(), other.deploymentName())
&& Objects.equals(arquillianDeploymentName(), other.arquillianDeploymentName());
}

@Override
public int hashCode() {
return Objects.hash(targetContainer(), deploymentName(), arquillianDeploymentName());
}

@Override
public String toString() {
return "TestDescription [targetContainer=" + targetContainer() + ", deploymentName=" + deploymentName()
+ ", arquillianDeploymentName=" + arquillianDeploymentName() + "]";
}

private static void addTestDescription(final Archive<?> archive, final String targetContainer,
final String arqDeploymentName) {
try {
final Properties properties = new Properties();
if (archive.contains(TestDescription.PATH)) {
try (InputStream in = archive.delete(TestDescription.PATH).getAsset().openStream()) {
properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
}
}
if (targetContainer != null) {
properties.put(TestDescription.TARGET_CONTAINER, targetContainer);
}
if (arqDeploymentName != null) {
properties.put(TestDescription.ARQ_DEPLOYMENT_NAME, arqDeploymentName);
}
properties.put(TestDescription.DEPLOYMENT_NAME, archive.getName());
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
properties.store(out, null);
archive.add(new ByteArrayAsset(out.toByteArray()), TestDescription.PATH);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import org.jboss.arquillian.container.test.spi.util.ServiceLoader;
import org.jboss.arquillian.testenricher.msc.ServiceTargetAssociation;
import org.jboss.as.arquillian.protocol.jmx.TestDescription;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.modules.Module;
Expand All @@ -46,13 +49,13 @@ public final class ArquillianConfig implements Service {
private final Supplier<ArquillianService> arquillianServiceSupplier;
private final Supplier<DeploymentUnit> deploymentUnitSupplier;
private final ServiceName serviceName;
private final List<String> testClasses = new ArrayList<>();
private final Map<String, TestClassInfo> testClasses;

ArquillianConfig(final ServiceName serviceName, final Set<String> testClasses,
ArquillianConfig(final ServiceName serviceName, final Map<String, TestClassInfo> testClasses,
final Supplier<ArquillianService> arquillianServiceSupplier,
final Supplier<DeploymentUnit> deploymentUnitSupplier) {
this.serviceName = serviceName;
this.testClasses.addAll(testClasses);
this.testClasses = Map.copyOf(testClasses);
this.arquillianServiceSupplier = arquillianServiceSupplier;
this.deploymentUnitSupplier = deploymentUnitSupplier;
for (ArquillianConfigServiceCustomizer customizer : ServiceLoader.load(ArquillianConfigServiceCustomizer.class)) {
Expand All @@ -74,12 +77,38 @@ ServiceName getServiceName() {
return serviceName;
}

List<String> getTestClasses() {
return Collections.unmodifiableList(testClasses);
/**
* Gets whether this config supports the given test class.
*
* @param className the name of the test class. Cannot be {@code null}
*
* @return {@code true} if this config supports a test class with the given classname
*/
boolean supports(String className) {
return testClasses.containsKey(className);
}

/**
* Gets whether this config supports the given test class and method. The method is considered supported
* if the class either has no methods annotated with {@code OperateOnDeployment} or it has at least one
* method with that annotation where the annotation value specifies this deployment.
* <p>
* Note that a return value of {@code true} does not guarantee that a method named {@code methodName} exists.
*
* @param className the name of the test class. Cannot be {@code null}
* @param methodName the name of the test class. Cannot be {@code null}
*
* @return {@code true} if this config supports a test class with the given classname and method name
*/
boolean supports(String className, String methodName) {
final TestClassInfo testClassInfo = testClasses.get(className);
return testClassInfo != null && (getDeploymentUnit().getName()
.equals(testClassInfo.testDescription.deploymentName())
&& testClassInfo.supportsMethod(methodName));
}

Class<?> loadClass(String className) throws ClassNotFoundException {
if (!testClasses.contains(className))
if (!testClasses.containsKey(className))
throw new ClassNotFoundException("Class '" + className + "' not found in: " + testClasses);

final Module module = deploymentUnitSupplier.get().getAttachment(Attachments.MODULE);
Expand All @@ -95,7 +124,7 @@ Class<?> loadClass(String className) throws ClassNotFoundException {
@Override
public void start(final StartContext context) {
arquillianServiceSupplier.get().registerArquillianConfig(this);
for (final String testClass : testClasses) {
for (final String testClass : testClasses.keySet()) {
ServiceTargetAssociation.setServiceTarget(testClass, context.getChildTarget());
}
}
Expand All @@ -104,7 +133,7 @@ public void start(final StartContext context) {
public void stop(final StopContext context) {
context.getController().setMode(Mode.REMOVE);
arquillianServiceSupplier.get().unregisterArquillianConfig(this);
for (final String testClass : testClasses) {
for (final String testClass : testClasses.keySet()) {
ServiceTargetAssociation.clearServiceTarget(testClass);
}
}
Expand All @@ -115,4 +144,27 @@ public String toString() {
final String sname = serviceName.getCanonicalName();
return "ArquillianConfig[service=" + sname + ",unit=" + uname + ",tests=" + testClasses + "]";
}

static final class TestClassInfo {

private final boolean allMethods;
private final TestDescription testDescription;
private final Set<String> methods;

TestClassInfo(final TestDescription testDescription) {
this.allMethods = true;
this.testDescription = testDescription;
this.methods = Collections.emptySet();
}

TestClassInfo(final TestDescription testDescription, final Set<String> methods) {
this.allMethods = false;
this.testDescription = testDescription;
this.methods = new HashSet<>(methods);
}

private boolean supportsMethod(final String methodName) {
return allMethods || methods.contains(methodName);
}
}
}
Loading

0 comments on commit 5a5cd00

Please sign in to comment.