diff --git a/junit-api/README.adoc b/junit-api/README.adoc
new file mode 100644
index 00000000..0e54f448
--- /dev/null
+++ b/junit-api/README.adoc
@@ -0,0 +1,48 @@
+= WildFly Arquillian JUnit 5 Utilities
+
+This library includes some utility for running Arquillian tests on WildFly with JUnit 5.
+
+== Annotations
+
+=== `@WildFlyArquillian`
+
+The `@WildFlyArquillian` annotation is a simple shorthand annotation for `@ExtendsWith(ArquillianExtension.class)`.
+
+=== `@RequiresModule`
+
+The `@RequiresModule` annotation can be used on a class or test method and will disable a test if the required module
+is not found. There is also an option to define minimum version of a module required for the test to be enabled.
+
+.Example
+[source,java]
+----
+import org.junit.jupiter.api.Test;import org.wildfly.arquillian.junit.annotations.RequiresModule;@WildFlyArquillian
+@RequiresModule("org.jboss.as.ejb3")
+public class Ejb3Test {
+
+ @Inject
+ private TestEjb ejb;
+
+ @Test
+ public void testEjb() {
+ Assertions.assertNotNull(ejb);
+ }
+
+ @Test
+ @RequiresModule(value = "org.jboss.as.ejb3", minVersion = "32.0.0.Beta1",
+ issueRef = "https://issues.redhat.com/browse/WFLY-1", reason = "This test only works on WildFly 32 or higher")
+ public void ejbNewFeature() {
+
+ }
+}
+----
+
+The `minVersion`, `issueRef` and `reason` are all optional. The `value`, which is the module name, is required. The
+`issueRef` and `reason` are used for the reason text when the test is disabled.
+
+=== `@JBossHome`
+
+The `@JBossHome` annotation is a simple helper annotation for injecting a `java.lang.String`, `java.nio.file.Path` or
+`java.io.File` parameter with the JBoss Home directory. First the `jboss.home` system property is checked. If not found,
+the `JBOSS_HOME` environment variable is checked. Finally, the `jboss.home.dir` is checked. If neither are set a
+`org.junit.jupiter.api.extension.ParameterResolutionException` is thrown.
\ No newline at end of file
diff --git a/junit-api/pom.xml b/junit-api/pom.xml
new file mode 100644
index 00000000..5c6b480c
--- /dev/null
+++ b/junit-api/pom.xml
@@ -0,0 +1,174 @@
+
+
+
+
+ 4.0.0
+
+ org.wildfly.arquillian
+ wildfly-arquillian-parent
+ 5.1.0.Final-SNAPSHOT
+
+
+ wildfly-arquillian-junit-api
+ WildFly: Arquillian JUnit API's
+
+
+ ${project.build.directory}/server
+
+
+
+
+ org.jboss.arquillian.container
+ arquillian-container-test-spi
+
+
+ org.jboss.arquillian.junit5
+ arquillian-junit5-core
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.wildfly.plugins
+ wildfly-plugin-tools
+
+
+
+
+ jakarta.ejb
+ jakarta.ejb-api
+ test
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+ test
+
+
+ jakarta.inject
+ jakarta.inject-api
+ test
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ test
+
+
+ org.jboss.arquillian.junit5
+ arquillian-junit5-container
+ test
+
+
+ org.jboss.arquillian.protocol
+ arquillian-protocol-servlet-jakarta
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.platform
+ junit-platform-testkit
+ test
+
+
+ org.wildfly.arquillian
+ wildfly-arquillian-container-managed
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ default-test
+
+
+ ${project.build.testOutputDirectory}/fake-modules
+
+ system.property
+
+
+
+ env-var
+ test
+
+ test
+
+
+
+ ${project.build.testOutputDirectory}/fake-modules
+
+ env.var
+
+
+
+
+
+ maven-failsafe-plugin
+
+
+ ${jboss.home}
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+
+
+ provision-server
+ pre-integration-test
+
+ provision
+
+
+ ${jboss.home}
+
+
+ wildfly@maven(org.jboss.universe:community-universe)#${version.org.wildfly.full}
+
+
+ ee-core-profile-server
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/JBossHome.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/JBossHome.java
new file mode 100644
index 00000000..e6eb615d
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/JBossHome.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wildfly.arquillian.junit.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.wildfly.arquillian.junit.condition.JBossHomeParameterResolver;
+
+/**
+ * Sets a parameter value to the path of the JBoss Home directory. The parameter can be a {@link java.nio.file.Path},
+ * {@link java.io.File} or {@link String}.
+ *
+ * @author James R. Perkins
+ */
+@Inherited
+@Documented
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(JBossHomeParameterResolver.class)
+public @interface JBossHome {
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/RequiresModule.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/RequiresModule.java
new file mode 100644
index 00000000..b53d4e16
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/RequiresModule.java
@@ -0,0 +1,75 @@
+/*
+ * 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.wildfly.arquillian.junit.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.wildfly.arquillian.junit.condition.RequiresModuleExecutionCondition;
+
+/**
+ * Enables or disables a test based on whether the module exists. You can optionally check the version of the module
+ * to determine if the modules version is greater than the {@linkplain #minVersion() minimum version}.
+ *
+ * @author James R. Perkins
+ */
+@Inherited
+@Documented
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(RequiresModuleExecutionCondition.class)
+public @interface RequiresModule {
+
+ /**
+ * The minimum version of the module resource.
+ *
+ * Note that if more than one resource is defined, only the first resource is used to determine the version.
+ *
+ *
+ * @return the minimum version
+ */
+ String minVersion() default "";
+
+ /**
+ * A reference for the issue tracker to be reported in the response for a disabled test.
+ *
+ * @return the issue reference
+ */
+ String issueRef() default "";
+
+ /**
+ * The reason message for disabled test.
+ *
+ * @return the reason message
+ */
+ String reason() default "";
+
+ /**
+ * The module that is required for the test to run.
+ *
+ * @return the module name
+ */
+ String value();
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/WildFlyArquillian.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/WildFlyArquillian.java
new file mode 100644
index 00000000..4afd29df
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/annotations/WildFlyArquillian.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wildfly.arquillian.junit.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Enables the test as an Arquillian test. This is short for annotating a test with
+ * {@code @ExtendWith(ArquillianExtension.class}.
+ *
+ * @author James R. Perkins
+ */
+@Inherited
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(ArquillianExtension.class)
+public @interface WildFlyArquillian {
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/JBossHomeParameterResolver.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/JBossHomeParameterResolver.java
new file mode 100644
index 00000000..17eafbe8
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/JBossHomeParameterResolver.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wildfly.arquillian.junit.condition;
+
+import java.io.File;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.wildfly.arquillian.junit.annotations.JBossHome;
+
+/**
+ * Resolves the {@code jboss.home} system property or if not set the {@code JBOSS_HOME} environment variable. If neither
+ * are set a {@link ParameterResolutionException} is thrown.
+ *
+ * @author James R. Perkins
+ */
+public class JBossHomeParameterResolver implements ParameterResolver {
+ @Override
+ public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ // Ignore parameters not annotated with @JBossHome
+ if (!parameterContext.isAnnotated(JBossHome.class)) {
+ return false;
+ }
+ final Class> parameterType = parameterContext.getParameter().getType();
+ return parameterType.isAssignableFrom(Path.class) || parameterType.isAssignableFrom(String.class)
+ || parameterType.isAssignableFrom(File.class);
+ }
+
+ @Override
+ public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ final String value = SecurityActions.resolveJBossHome();
+ if (value == null) {
+ throw new ParameterResolutionException(
+ "Could not resolve the jboss.home system property or JBOSS_HOME environment variable.");
+ }
+ final Path path = Path.of(value).toAbsolutePath();
+ final Class> parameterType = parameterContext.getParameter().getType();
+ if (parameterType.isAssignableFrom(Path.class)) {
+ return path;
+ }
+ if (parameterType.isAssignableFrom(String.class)) {
+ return path.toString();
+ }
+ if (parameterType.isAssignableFrom(File.class)) {
+ return path.toFile();
+ }
+ throw new ParameterResolutionException(
+ "Cannot convert the JBoss Home directory into a type of " + parameterType.getName());
+ }
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/RequiresModuleExecutionCondition.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/RequiresModuleExecutionCondition.java
new file mode 100644
index 00000000..2b6c21a5
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/RequiresModuleExecutionCondition.java
@@ -0,0 +1,213 @@
+/*
+ * 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.wildfly.arquillian.junit.condition;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.platform.commons.support.AnnotationSupport;
+import org.wildfly.arquillian.junit.annotations.RequiresModule;
+import org.wildfly.plugin.tools.VersionComparator;
+import org.xml.sax.SAXException;
+
+/**
+ * Evaluates conditions that a module exists with the minimum version, if defined.
+ *
+ * @author James R. Perkins
+ */
+public class RequiresModuleExecutionCondition implements ExecutionCondition {
+
+ @Override
+ public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) {
+ return AnnotationSupport.findAnnotation(context.getElement(), RequiresModule.class)
+ .map((this::checkModule))
+ .orElse(ConditionEvaluationResult
+ .enabled("Could not determine the @RequiresModule was found, enabling by default"));
+ }
+
+ private ConditionEvaluationResult checkModule(final RequiresModule requiresModule) {
+ // First check for the module.path, if not set use the JBoss Home resolution
+ final Path moduleDir = resolveModulesDir();
+ // Not set, do not disable the test
+ if (moduleDir == null) {
+ return ConditionEvaluationResult.enabled("The module directory could not be resolved.");
+ }
+
+ try {
+ // Get the module XML file.
+ final Optional moduleXmlFile = findModuleXml(moduleDir, moduleToPath(requiresModule.value()));
+ if (moduleXmlFile.isPresent()) {
+ if (requiresModule.minVersion().isBlank()) {
+ return ConditionEvaluationResult
+ .enabled(formatReason(requiresModule, "Module %s found in %s. Enabling test.",
+ requiresModule.value(), moduleXmlFile.get()));
+ }
+ return checkVersion(requiresModule, moduleXmlFile.get());
+ }
+ } catch (IOException e) {
+ return ConditionEvaluationResult
+ .enabled("Could not find module " + requiresModule.value() + ". Enabling by default. Reason: "
+ + e.getMessage());
+ }
+ return ConditionEvaluationResult
+ .disabled(
+ formatReason(requiresModule, "Module %s not found in %s. Disabling test.", requiresModule.value(),
+ moduleDir));
+ }
+
+ private ConditionEvaluationResult checkVersion(final RequiresModule requiresModule, final Path moduleXmlFile) {
+ try {
+ // Resolve the version from the module.xml file
+ final String version = version(moduleXmlFile);
+ // Likely indicates the version could not be resolved.
+ if (version.isBlank()) {
+ return ConditionEvaluationResult
+ .enabled(String.format("Could not determine version of module %s", moduleXmlFile));
+ }
+ if (isAtLeastVersion(requiresModule.minVersion(), version)) {
+ return ConditionEvaluationResult
+ .enabled(String.format("Found version %s and required a minimum of version %s. Enabling tests.",
+ version, requiresModule.minVersion()));
+ }
+ return ConditionEvaluationResult
+ .disabled(formatReason(requiresModule,
+ "Found version %s and required a minimum of version %s. Disabling test.", version,
+ requiresModule.minVersion()));
+ } catch (IOException e) {
+ return ConditionEvaluationResult
+ .enabled(String.format("Could not determine the version for module %s. Enabling by default. Reason: %s",
+ requiresModule.value(), e.getMessage()));
+ }
+ }
+
+ private static String moduleToPath(final String moduleName) {
+ return String.join(File.separator, moduleName.split("\\."));
+ }
+
+ private static String version(final Path moduleXmlFile) throws IOException {
+ try (InputStream in = Files.newInputStream(moduleXmlFile)) {
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ final org.w3c.dom.Document document = builder.parse(in);
+ final var resources = document.getElementsByTagName("resources");
+ if (resources.getLength() > 0) {
+ // Use only the first resources, which there should only be one of
+ final var resource = resources.item(0);
+ final var nodes = resource.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final var node = nodes.item(i);
+ if (node.getNodeName().equals("artifact")) {
+ // Use the Maven GAV where the third entry should be the version
+ final var name = node.getAttributes().getNamedItem("name").getTextContent();
+ final var gav = name.split(":");
+ if (gav.length > 2) {
+ return sanitizeVersion(gav[2]);
+ }
+ break;
+ } else if (node.getNodeName().equals("resource-root")) {
+ final String path = node.getAttributes().getNamedItem("path").getTextContent();
+ final Path parent = moduleXmlFile.getParent();
+ final Path jar = parent == null ? Path.of(path) : parent.resolve(path);
+ try (JarFile jarFile = new JarFile(jar.toFile())) {
+ return extractVersionFromManifest(jarFile);
+ }
+ }
+ }
+ }
+ } catch (ParserConfigurationException | SAXException e) {
+ throw new IOException("Failed to parse module XML file " + moduleXmlFile, e);
+ }
+ return "";
+ }
+
+ private static String extractVersionFromManifest(final JarFile jarFile) throws IOException {
+ final Manifest manifest = jarFile.getManifest();
+ final var version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ return sanitizeVersion(version);
+ }
+
+ private static String sanitizeVersion(final String version) {
+ if (version == null) {
+ return "";
+ }
+ // Skip the "-redhat" for our purposes
+ final int end = version.indexOf("-redhat");
+ if (end > 0) {
+ return version.substring(0, end);
+ }
+ return version;
+ }
+
+ private static Optional findModuleXml(final Path dir, final String pathName) throws IOException {
+ try (Stream files = Files.walk(dir)) {
+ return files.filter((f) -> f.toString().contains(pathName)
+ && f.getFileName().toString().equals("module.xml")).findFirst();
+ }
+ }
+
+ private static boolean isAtLeastVersion(final String minVersion, final String foundVersion) {
+ if (foundVersion == null) {
+ return false;
+ }
+ return VersionComparator.compareVersion(foundVersion, minVersion) >= 0;
+ }
+
+ private static String formatReason(final RequiresModule requiresModule, final String fmt, final Object... args) {
+ String msg = String.format(fmt, args);
+ if (!requiresModule.issueRef().isBlank()) {
+ msg = requiresModule.issueRef() + ": " + msg;
+ }
+ if (!requiresModule.reason().isBlank()) {
+ msg = msg + " Reason: " + requiresModule.reason();
+ }
+ return msg;
+ }
+
+ private static Path resolveModulesDir() {
+ final String moduleDir = SecurityActions.getSystemProperty("module.path");
+ if (moduleDir != null) {
+ return Path.of(moduleDir);
+ }
+ final String jbossHome = SecurityActions.resolveJBossHome();
+ if (jbossHome == null) {
+ return null;
+ }
+ return Path.of(jbossHome, "modules");
+ }
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/SecurityActions.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/SecurityActions.java
new file mode 100644
index 00000000..e6ec485f
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/condition/SecurityActions.java
@@ -0,0 +1,62 @@
+/*
+ * 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.wildfly.arquillian.junit.condition;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Not to be made public.
+ *
+ * @author James R. Perkins
+ */
+class SecurityActions {
+
+ static String getSystemProperty(final String name) {
+ if (System.getSecurityManager() == null) {
+ return System.getProperty(name);
+ }
+ return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(name));
+ }
+
+ static String resolveJBossHome() {
+ // We optionally do this privileged for in-container tests
+ if (System.getSecurityManager() == null) {
+ String value = System.getProperty("jboss.home");
+ if (value == null) {
+ value = System.getenv("JBOSS_HOME");
+ }
+ if (value == null) {
+ value = System.getProperty("jboss.home.dir");
+ }
+ return value;
+ }
+ return AccessController.doPrivileged((PrivilegedAction) () -> {
+ String value = System.getProperty("jboss.home");
+ if (value == null) {
+ value = System.getenv("JBOSS_HOME");
+ }
+ if (value == null) {
+ value = System.getProperty("jboss.home.dir");
+ }
+ return value;
+ });
+ }
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/EnricherExtension.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/EnricherExtension.java
new file mode 100644
index 00000000..b02e3219
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/EnricherExtension.java
@@ -0,0 +1,33 @@
+/*
+ * 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.wildfly.arquillian.junit.enricher;
+
+import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
+import org.jboss.arquillian.core.spi.LoadableExtension;
+
+/**
+ * @author James R. Perkins
+ */
+public class EnricherExtension implements LoadableExtension {
+ @Override
+ public void register(final ExtensionBuilder builder) {
+ builder.service(AuxiliaryArchiveAppender.class, ExtensionAuxiliaryArchiveAppender.class);
+ }
+}
diff --git a/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/ExtensionAuxiliaryArchiveAppender.java b/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/ExtensionAuxiliaryArchiveAppender.java
new file mode 100644
index 00000000..603a6d6b
--- /dev/null
+++ b/junit-api/src/main/java/org/wildfly/arquillian/junit/enricher/ExtensionAuxiliaryArchiveAppender.java
@@ -0,0 +1,41 @@
+/*
+ * 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.wildfly.arquillian.junit.enricher;
+
+import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.wildfly.arquillian.junit.annotations.JBossHome;
+import org.wildfly.arquillian.junit.condition.JBossHomeParameterResolver;
+import org.wildfly.plugin.tools.VersionComparator;
+
+/**
+ * @author James R. Perkins
+ */
+public class ExtensionAuxiliaryArchiveAppender implements AuxiliaryArchiveAppender {
+ @Override
+ public Archive> createAuxiliaryArchive() {
+ return ShrinkWrap.create(JavaArchive.class, "wildfly-junit-utils.jar")
+ .addPackage(JBossHome.class.getPackage())
+ .addPackage(JBossHomeParameterResolver.class.getPackage())
+ .addClass(VersionComparator.class);
+ }
+}
diff --git a/junit-api/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/junit-api/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
new file mode 100644
index 00000000..b1f206d0
--- /dev/null
+++ b/junit-api/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension
@@ -0,0 +1 @@
+org.wildfly.arquillian.junit.enricher.EnricherExtension
\ No newline at end of file
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/ClientJBossHomeIT.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/ClientJBossHomeIT.java
new file mode 100644
index 00000000..c271667a
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/ClientJBossHomeIT.java
@@ -0,0 +1,41 @@
+/*
+ * 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.wildfly.arquillian.junit.integration;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.WildFlyArquillian;
+
+/**
+ * @author James R. Perkins
+ */
+@WildFlyArquillian
+@RunAsClient
+public class ClientJBossHomeIT extends InContainerJBossHomeIT {
+
+ @Test
+ @Override
+ public void assertEnvironment() {
+ final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ Assertions.assertNotEquals("deployment." + InContainerJBossHomeIT.class.getSimpleName() + ".war",
+ classLoader.getName());
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerJBossHomeIT.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerJBossHomeIT.java
new file mode 100644
index 00000000..2c2fbdf6
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerJBossHomeIT.java
@@ -0,0 +1,66 @@
+/*
+ * 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.wildfly.arquillian.junit.integration;
+
+import java.io.File;
+import java.nio.file.Path;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.JBossHome;
+import org.wildfly.arquillian.junit.annotations.WildFlyArquillian;
+
+/**
+ * @author James R. Perkins
+ */
+@WildFlyArquillian
+public class InContainerJBossHomeIT {
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class, InContainerJBossHomeIT.class.getSimpleName() + ".war")
+ .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
+ }
+
+ @Test
+ public void assertEnvironment() {
+ final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ Assertions.assertEquals("deployment." + InContainerJBossHomeIT.class.getSimpleName() + ".war", classLoader.getName());
+ }
+
+ @Test
+ public void stringSet(@JBossHome final String value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.lang.String");
+ }
+
+ @Test
+ public void pathSet(@JBossHome final Path value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.nio.file.Path");
+ }
+
+ @Test
+ public void fileSet(@JBossHome final File value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.io.File");
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerRequireModuleIT.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerRequireModuleIT.java
new file mode 100644
index 00000000..b251456f
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/InContainerRequireModuleIT.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wildfly.arquillian.junit.integration;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.RequiresModule;
+import org.wildfly.arquillian.junit.annotations.WildFlyArquillian;
+
+/**
+ * @author James R. Perkins
+ */
+@WildFlyArquillian
+@ApplicationScoped
+public class InContainerRequireModuleIT {
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class)
+ .addClass(Greeter.class)
+ .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
+ }
+
+ @Inject
+ private Greeter greeter;
+
+ @RequiresModule("org.jboss.as.ejb3")
+ @Test
+ public void expectSkipped() {
+ Assertions.fail("Test should have been skipped.");
+ }
+
+ @RequiresModule(value = "jakarta.ws.rs.api", minVersion = "3.1.0")
+ @Test
+ public void pass() {
+ Assertions.assertNotNull(greeter);
+ Assertions.assertEquals("Hello!", greeter.hello());
+ }
+
+ @ApplicationScoped
+ public static class Greeter {
+
+ public String hello() {
+ return "Hello!";
+ }
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/RequiresModuleIT.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/RequiresModuleIT.java
new file mode 100644
index 00000000..6759fc0e
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/integration/RequiresModuleIT.java
@@ -0,0 +1,62 @@
+/*
+ * 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.wildfly.arquillian.junit.integration;
+
+import jakarta.ejb.Stateless;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.RequiresModule;
+import org.wildfly.arquillian.junit.annotations.WildFlyArquillian;
+
+/**
+ * @author James R. Perkins
+ */
+@WildFlyArquillian
+@RequiresModule("org.jboss.as.ejb3")
+public class RequiresModuleIT {
+
+ @Inject
+ private SimpleEjb simpleEjb;
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class)
+ .addClass(SimpleEjb.class)
+ .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xlm");
+ }
+
+ @Test
+ public void ejbNotNull() {
+ Assertions.fail("Test should have been skipped.");
+ }
+
+ @ApplicationScoped
+ @Stateless
+ public static class SimpleEjb {
+
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/jboss/home/JBossHomeParameterTestCase.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/jboss/home/JBossHomeParameterTestCase.java
new file mode 100644
index 00000000..5ef81211
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/jboss/home/JBossHomeParameterTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * 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.wildfly.arquillian.junit.jboss.home;
+
+import java.io.File;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.JBossHome;
+
+/**
+ *
+ * @author James R. Perkins
+ */
+@Tag("env.var")
+@Tag("system.property")
+public class JBossHomeParameterTestCase {
+
+ @Test
+ public void stringSet(@JBossHome final String value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.lang.String");
+ }
+
+ @Test
+ public void pathSet(@JBossHome final Path value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.nio.file.Path");
+ }
+
+ @Test
+ public void fileSet(@JBossHome final File value) {
+ Assertions.assertNotNull(value, "Expected the JBossHome to be set to a java.io.File");
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireArtifact.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireArtifact.java
new file mode 100644
index 00000000..905bd3ab
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireArtifact.java
@@ -0,0 +1,45 @@
+/*
+ * 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.wildfly.arquillian.junit.requires.module;
+
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.RequiresModule;
+
+/**
+ * @author James R. Perkins
+ */
+@SuppressWarnings("NewClassNamingConvention")
+@RequiresModule("org.wildfly.arquillian.junit.test.artifact")
+public class RequireArtifact {
+
+ @Test
+ public void passing() {
+ }
+
+ @Test
+ @RequiresModule(value = "org.wildfly.arquillian.junit.test.artifact", minVersion = "2.0.0")
+ public void skippedVersion() {
+ }
+
+ @Test
+ @RequiresModule(value = "org.wildfly.arquillian.junit.test.artifact.invalid")
+ public void skippedMissingModule() {
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireResourceRoot.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireResourceRoot.java
new file mode 100644
index 00000000..f23ead33
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequireResourceRoot.java
@@ -0,0 +1,50 @@
+/*
+ * 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.wildfly.arquillian.junit.requires.module;
+
+import org.junit.jupiter.api.Test;
+import org.wildfly.arquillian.junit.annotations.RequiresModule;
+
+/**
+ * @author James R. Perkins
+ */
+@SuppressWarnings("NewClassNamingConvention")
+@RequiresModule("org.wildfly.arquillian.junit.test.resource-root")
+public class RequireResourceRoot {
+
+ @Test
+ public void passing() {
+ }
+
+ @Test
+ @RequiresModule(value = "org.wildfly.arquillian.junit.test.resource-root", minVersion = "1.0.0")
+ public void passingVersion() {
+ }
+
+ @Test
+ @RequiresModule(value = "org.wildfly.arquillian.junit.test.resource-root", minVersion = "2.0.1")
+ public void skippedVersion() {
+ }
+
+ @Test
+ @RequiresModule(value = "org.wildfly.arquillian.junit.test.resource-root.invalid")
+ public void skippedMissingModule() {
+ }
+}
diff --git a/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequiresModuleTestCase.java b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequiresModuleTestCase.java
new file mode 100644
index 00000000..0a539e37
--- /dev/null
+++ b/junit-api/src/test/java/org/wildfly/arquillian/junit/requires/module/RequiresModuleTestCase.java
@@ -0,0 +1,145 @@
+/*
+ * 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.wildfly.arquillian.junit.requires.module;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.platform.engine.discovery.DiscoverySelectors;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.EventConditions;
+import org.wildfly.arquillian.junit.annotations.JBossHome;
+
+/**
+ * Tests for the {@link org.wildfly.arquillian.junit.annotations.RequiresModule} annotation.
+ *
+ * @author James R. Perkins
+ */
+@Tag("env.var")
+@Tag("system.property")
+public class RequiresModuleTestCase {
+
+ @BeforeAll
+ public static void setup(@JBossHome final Path jbossHome) throws Exception {
+ // Create the JAR with a manifest only
+ final Path jarPath = jbossHome.resolve(
+ Path.of("modules", "org", "wildfly", "arquillian", "junit", "test", "resource-root", "main",
+ "test-2.0.0.Final.jar"));
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ manifest.getMainAttributes().put(Attributes.Name.IMPLEMENTATION_VERSION, "2.0.0.Final");
+ try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) {
+ // Simply flush to write the manifest
+ out.flush();
+ }
+ }
+
+ @Test
+ public void artifactPassed() {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireArtifact.class, "passing"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.succeeded(1L));
+ }
+
+ @Test
+ public void artifactSkippedVersion() {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireArtifact.class, "skippedVersion"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.skipped(1L));
+ testEvents.assertThatEvents().haveExactly(1, EventConditions.event(
+ EventConditions.skippedWithReason(
+ "Found version 1.0.0.Final and required a minimum of version 2.0.0. Disabling test.")));
+ }
+
+ @Test
+ public void artifactSkippedMissingModule(@JBossHome final Path jbossHome) {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireArtifact.class, "skippedMissingModule"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.skipped(1L));
+ testEvents.assertThatEvents().haveExactly(1, EventConditions.event(
+ EventConditions.skippedWithReason(
+ String.format(
+ "Module org.wildfly.arquillian.junit.test.artifact.invalid not found in %s. Disabling test.",
+ jbossHome.resolve("modules")))));
+ }
+
+ @Test
+ public void resourceRootPassed() {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireResourceRoot.class, "passing"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.succeeded(1L));
+ }
+
+ @Test
+ public void resourceRootPassedVersion() {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireResourceRoot.class, "passingVersion"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.succeeded(1L));
+ }
+
+ @Test
+ public void resourceRootSkippedVersion() {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireResourceRoot.class, "skippedVersion"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.skipped(1L));
+ testEvents.assertThatEvents().haveExactly(1, EventConditions.event(
+ EventConditions.skippedWithReason(
+ "Found version 2.0.0.Final and required a minimum of version 2.0.1. Disabling test.")));
+ }
+
+ @Test
+ public void resourceRootSkippedMissingModule(@JBossHome final Path jbossHome) {
+ final var testEvents = EngineTestKit.engine("junit-jupiter")
+ .selectors(DiscoverySelectors.selectMethod(RequireResourceRoot.class, "skippedMissingModule"))
+ .execute()
+ .testEvents();
+
+ testEvents.assertStatistics((stats) -> stats.skipped(1L));
+ testEvents.assertThatEvents().haveExactly(1, EventConditions.event(
+ EventConditions.skippedWithReason(
+ String.format(
+ "Module org.wildfly.arquillian.junit.test.resource-root.invalid not found in %s. Disabling test.",
+ jbossHome.resolve("modules")))));
+ }
+}
diff --git a/junit-api/src/test/resources/arquillian.xml b/junit-api/src/test/resources/arquillian.xml
new file mode 100644
index 00000000..d78866f7
--- /dev/null
+++ b/junit-api/src/test/resources/arquillian.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+ ${jboss.home}
+ ${debug.vm.args} ${jvm.args}
+
+ false
+
+
+
+
diff --git a/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/artifact/main/module.xml b/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/artifact/main/module.xml
new file mode 100644
index 00000000..fc2ce9e9
--- /dev/null
+++ b/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/artifact/main/module.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/resource-root/main/module.xml b/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/resource-root/main/module.xml
new file mode 100644
index 00000000..76d79d15
--- /dev/null
+++ b/junit-api/src/test/resources/fake-modules/modules/org/wildfly/arquillian/junit/test/resource-root/main/module.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ca247ecd..4f8767fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,7 +41,7 @@
30.0.0.Final
3.1.6
4.13.2
- 5.9.1
+ 5.10.2
1.8.0.Final
1.8.0.Final
5.2.2.Final
@@ -69,6 +69,10 @@
5.0.0
1.0.8.Final
+
+ 3.1.0
+ 6.2.7.Final
+
true
true
@@ -299,6 +303,7 @@
common-domain
container-remote-domain
container-managed-domain
+ junit-api
integration-tests
@@ -313,6 +318,13 @@
pom
import