Skip to content

Commit

Permalink
[WFARQ-173] Ensure we get the correct deployment for the method and t…
Browse files Browse the repository at this point in the history
…est.

https://issues.redhat.com/browse/WFARQ-173
Signed-off-by: James R. Perkins <[email protected]>
  • Loading branch information
jamezp committed Jun 6, 2024
1 parent ecf3d57 commit d5805f1
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 40 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 @@ -18,14 +18,14 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
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 @@ -49,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 Map<String, ArquillianConfig.TestClassMethods> testClasses = new LinkedHashMap<>();
private final Map<String, TestClassInfo> testClasses;

ArquillianConfig(final ServiceName serviceName, final Map<String, TestClassMethods> testClasses,
ArquillianConfig(final ServiceName serviceName, final Map<String, TestClassInfo> testClasses,
final Supplier<ArquillianService> arquillianServiceSupplier,
final Supplier<DeploymentUnit> deploymentUnitSupplier) {
this.serviceName = serviceName;
this.testClasses.putAll(testClasses);
this.testClasses = Map.copyOf(testClasses);
this.arquillianServiceSupplier = arquillianServiceSupplier;
this.deploymentUnitSupplier = deploymentUnitSupplier;
for (ArquillianConfigServiceCustomizer customizer : ServiceLoader.load(ArquillianConfigServiceCustomizer.class)) {
Expand Down Expand Up @@ -101,8 +101,10 @@ boolean supports(String className) {
* @return {@code true} if this config supports a test class with the given classname and method name
*/
boolean supports(String className, String methodName) {
TestClassMethods methods = testClasses.get(className);
return methods != null && methods.supportsMethod(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 {
Expand Down Expand Up @@ -143,24 +145,25 @@ public String toString() {
return "ArquillianConfig[service=" + sname + ",unit=" + uname + ",tests=" + testClasses + "]";
}

static final class TestClassMethods {

static final TestClassMethods ALL_METHODS = new TestClassMethods();
static final class TestClassInfo {

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

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

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

private boolean supportsMethod(String methodName) {
private boolean supportsMethod(final String methodName) {
return allMethods || methods.contains(methodName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.Set;

import org.jboss.as.arquillian.protocol.jmx.TestDescription;
import org.jboss.as.server.deployment.AttachmentKey;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentUnit;
Expand Down Expand Up @@ -61,15 +62,15 @@ class ArquillianConfigBuilder {
private static final DotName OPERATE_ON_DEPLOYMENT = DotName
.createSimple("org.jboss.arquillian.container.test.api.OperateOnDeployment");

private static final AttachmentKey<Map<String, ArquillianConfig.TestClassMethods>> CLASSES = AttachmentKey
private static final AttachmentKey<Map<String, ArquillianConfig.TestClassInfo>> CLASSES = AttachmentKey
.create(Map.class);

ArquillianConfigBuilder() {
}

static Map<String, ArquillianConfig.TestClassMethods> getClasses(final DeploymentUnit depUnit) {
static Map<String, ArquillianConfig.TestClassInfo> getClasses(final DeploymentUnit depUnit) {
// Get Test Class Names
final Map<String, ArquillianConfig.TestClassMethods> testClasses = depUnit.getAttachment(CLASSES);
final Map<String, ArquillianConfig.TestClassInfo> testClasses = depUnit.getAttachment(CLASSES);
return testClasses == null || testClasses.isEmpty() ? null : testClasses;
}

Expand Down Expand Up @@ -108,37 +109,57 @@ static void handleParseAnnotations(final DeploymentUnit deploymentUnit) {
final Set<ClassInfo> testNgTests = compositeIndex.getAllKnownSubclasses(testNGClassName);

// Get Test Class Names
final Map<String, ArquillianConfig.TestClassMethods> testClasses = new LinkedHashMap<>();
final Map<String, ArquillianConfig.TestClassInfo> testClasses = new LinkedHashMap<>();
final TestDescription testDescription = TestDescription.from(deploymentUnit);
// JUnit
for (AnnotationInstance instance : runWithList) {
final AnnotationTarget target = instance.target();
if (target instanceof ClassInfo) {
final ClassInfo classInfo = (ClassInfo) target;
final String testClassName = classInfo.name().toString();
testClasses.put(testClassName, getTestMethods(classInfo, deploymentUnit.getName()));
testClasses.put(testClassName,
getTestMethods(compositeIndex, classInfo, testDescription));
}
}
// TestNG
for (final ClassInfo classInfo : testNgTests) {
testClasses.put(classInfo.name().toString(), getTestMethods(classInfo, deploymentUnit.getName()));
testClasses.put(classInfo.name().toString(), getTestMethods(compositeIndex, classInfo, testDescription));
}
deploymentUnit.putAttachment(CLASSES, testClasses);
}

private static ArquillianConfig.TestClassMethods getTestMethods(ClassInfo classInfo, String deployment) {
private static ArquillianConfig.TestClassInfo getTestMethods(final CompositeIndex compositeIndex, final ClassInfo classInfo,
final TestDescription testDescription) {
// Record all methods which can operate on this deployment.
final Set<String> methods = new HashSet<>();
final String deploymentName = testDescription.arquillianDeploymentName().orElse(null);
findAllMethods(compositeIndex, classInfo, deploymentName, methods);
return new ArquillianConfig.TestClassInfo(testDescription, Set.copyOf(methods));
}

List<AnnotationInstance> instances = classInfo.annotations(OPERATE_ON_DEPLOYMENT);
if (instances.isEmpty()) {
return ArquillianConfig.TestClassMethods.ALL_METHODS;
private static void findAllMethods(final CompositeIndex compositeIndex, final ClassInfo classInfo,
final String deploymentName, final Set<String> methods) {
if (classInfo == null) {
return;
}

Set<String> methods = new HashSet<>();
for (AnnotationInstance instance : instances) {
if (deployment.equals(instance.value().asString())
&& instance.target().kind() == AnnotationTarget.Kind.METHOD) {
methods.add(instance.target().asMethod().name());
classInfo.methods().forEach(methodInfo -> {
// If the @OperateOnDeployment method is present, it must match the test descriptions deployment
if (methodInfo.hasAnnotation(OPERATE_ON_DEPLOYMENT)) {
final AnnotationInstance annotation = methodInfo.annotation(OPERATE_ON_DEPLOYMENT);
if (annotation.value().asString().equals(deploymentName)) {
methods.add(methodInfo.name());
}
} else {
// No @OperateOnDeployment annotation present on the method, we have to assume it's okay to run for
// this test description.
methods.add(methodInfo.name());
}
});
if (classInfo.superName() != null && !classInfo.superName().toString().equals(Object.class.getName())) {
findAllMethods(compositeIndex, compositeIndex.getClassByName(classInfo.superName()), deploymentName, methods);
}
return new ArquillianConfig.TestClassMethods(methods);
// Interfaces can have default methods, we'll check those too
classInfo.interfaceNames()
.forEach(name -> findAllMethods(compositeIndex, compositeIndex.getClassByName(name), deploymentName, methods));
}
}
Loading

0 comments on commit d5805f1

Please sign in to comment.