You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@deltaspike.apache.org by st...@apache.org on 2026/05/11 11:38:25 UTC
(deltaspike) 02/10: DELTASPIKE-1366 adding Junit5 support
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/deltaspike.git
commit d1359dc571083cc1bfaa28e2816cfc34a360232d
Author: Mark Struberg <st...@apache.org>
AuthorDate: Fri May 8 15:59:12 2026 +0200
DELTASPIKE-1366 adding Junit5 support
---
deltaspike/modules/pom.xml | 1 +
deltaspike/modules/test-control5/api/pom.xml | 70 +++
.../deltaspike/testcontrol5/api/TestControl.java | 79 +++
.../testcontrol5/api/junit/CdiTestExtension.java | 628 +++++++++++++++++++++
.../api/junit/CdiTestSuiteExtension.java | 258 +++++++++
.../testcontrol5/api/junit/TestBaseConfig.java | 71 +++
.../api/literal/TestControlLiteral.java | 76 +++
.../api/mock/ApplicationMockManager.java | 28 +
.../testcontrol5/api/mock/DynamicMockManager.java | 24 +
.../testcontrol5/api/mock/TypedMock.java | 34 ++
.../testcontrol5/spi/ExternalContainer.java | 34 ++
.../deltaspike/testcontrol5/spi/TestAware.java | 28 +
.../testcontrol5/spi/TestControlValidator.java | 27 +
.../spi/junit/TestStatementDecoratorFactory.java | 28 +
.../testcontrol5/spi/mock/MockFilter.java | 29 +
deltaspike/modules/test-control5/impl/pom.xml | 141 +++++
.../impl/mock/AbstractMockManager.java | 112 ++++
.../testcontrol5/impl/mock/BeanCacheKey.java | 265 +++++++++
.../testcontrol5/impl/mock/DefaultMockFilter.java | 172 ++++++
.../impl/mock/MockAwareInjectionTargetWrapper.java | 99 ++++
.../impl/mock/MockAwareProducerWrapper.java | 82 +++
.../testcontrol5/impl/mock/MockExtension.java | 157 ++++++
.../impl/mock/SimpleApplicationMockManager.java | 30 +
.../testcontrol5/impl/mock/SimpleMockManager.java | 47 ++
.../impl/request/ContextControlDecorator.java | 115 ++++
.../StandardContextTestControlValidator.java | 107 ++++
deltaspike/modules/{ => test-control5}/pom.xml | 21 +-
27 files changed, 2749 insertions(+), 14 deletions(-)
diff --git a/deltaspike/modules/pom.xml b/deltaspike/modules/pom.xml
index d4017033f..b448d066d 100644
--- a/deltaspike/modules/pom.xml
+++ b/deltaspike/modules/pom.xml
@@ -43,5 +43,6 @@
<module>data</module>
<module>scheduler</module>
<module>test-control</module>
+ <module>test-control5</module>
</modules>
</project>
diff --git a/deltaspike/modules/test-control5/api/pom.xml b/deltaspike/modules/test-control5/api/pom.xml
new file mode 100644
index 000000000..21ceb3e90
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>test-control5-module-project</artifactId>
+ <version>2.0.2-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>deltaspike-test-control5-module-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Apache DeltaSpike Test-Control5-Module API (JUnit 5)</name>
+
+ <properties>
+ <deltaspike.osgi.export.pkg>
+ org.apache.deltaspike.testcontrol5.*
+ </deltaspike.osgi.export.pkg>
+ <deltaspike.osgi.import>
+ jakarta.enterprise.inject,
+ !org.apache.deltaspike.testcontrol5.*,
+ *
+ </deltaspike.osgi.import>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.deltaspike.core</groupId>
+ <artifactId>deltaspike-core-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.deltaspike.cdictrl</groupId>
+ <artifactId>deltaspike-cdictrl-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>${junit5.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java
new file mode 100644
index 000000000..816866c3f
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/TestControl.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api;
+
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Optional control annotation for JUnit 5 unit-tests
+ */
+
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+public @interface TestControl
+{
+ /**
+ * only supports contexts supported by ContextControl#startContext
+ * defaults: session- and request-scope
+ */
+ Class<? extends Annotation>[] startScopes() default { };
+
+ //TODO discuss callbacks
+
+ Class<? extends ProjectStage> projectStage() default ProjectStage.UnitTest.class;
+
+ /**
+ * only supported on test-class-level
+ */
+ Class<? extends Handler> logHandler() default ConsoleHandler.class;
+
+ /**
+ * Requires additional service-loader config
+ * Currently only supported on class-level
+ */
+ boolean startExternalContainers() default true;
+
+ /**
+ * allows to label alternative cdi-beans similar to global alternatives to bind them to 0-n tests
+ */
+ Class<? extends Label> activeAlternativeLabel() default Label.class;
+
+ //with cdi 1.1+ it can be used to implement labeled-alternatives without text based config
+ //(details see DELTASPIKE-1338)
+ /**
+ * low-level filter (mainly needed for special cases if labeled-alternatives aren't enough)
+ * @return the class-filter class which should be used for the current test-class
+ */
+ Class<? extends ClassFilter> classFilter() default ClassFilter.class;
+
+ interface Label
+ {
+ }
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java
new file mode 100644
index 000000000..49aa23ea2
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestExtension.java
@@ -0,0 +1,628 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.cdise.api.CdiContainer;
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.cdise.api.ContextControl;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.core.util.ExceptionUtils;
+import org.apache.deltaspike.core.util.ProjectStageProducer;
+import org.apache.deltaspike.core.util.ServiceUtils;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.apache.deltaspike.testcontrol5.api.literal.TestControlLiteral;
+import org.apache.deltaspike.testcontrol5.spi.ExternalContainer;
+import org.apache.deltaspike.testcontrol5.spi.TestAware;
+import org.apache.deltaspike.testcontrol5.spi.TestControlValidator;
+import org.apache.deltaspike.testcontrol5.spi.junit.TestStatementDecoratorFactory;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+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 jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Singleton;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * A JUnit 5 extension to start up with a CDI or embedded JavaEE container.
+ */
+public class CdiTestExtension implements BeforeAllCallback, AfterAllCallback,
+ BeforeEachCallback, AfterEachCallback, ParameterResolver
+{
+ private static final Logger LOGGER = Logger.getLogger(CdiTestExtension.class.getName());
+
+ private static final boolean USE_TEST_CLASS_AS_CDI_BEAN;
+ private static final boolean ALLOW_INJECTION_POINT_MANIPULATION;
+
+ private static Set<Integer> extensionIdentities = new CopyOnWriteArraySet<Integer>();
+
+ static
+ {
+ USE_TEST_CLASS_AS_CDI_BEAN = TestBaseConfig.ContainerIntegration.USE_TEST_CLASS_AS_CDI_BEAN;
+ ALLOW_INJECTION_POINT_MANIPULATION = TestBaseConfig.MockIntegration.ALLOW_MANUAL_INJECTION_POINT_MANIPULATION;
+ }
+
+ private static ThreadLocal<Boolean> automaticScopeHandlingActive = new ThreadLocal<Boolean>();
+
+ private static ThreadLocal<CdiTestExtension> currentTestExtension = new ThreadLocal<CdiTestExtension>();
+
+ private List<TestStatementDecoratorFactory> statementDecoratorFactories;
+
+ private ContainerAwareTestContext testContext;
+
+ public CdiTestExtension()
+ {
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception
+ {
+ currentTestExtension.set(this);
+
+ TestControl testControl = extensionContext.getTestClass()
+ .map(cls -> cls.getAnnotation(TestControl.class)).orElse(null);
+
+ ContainerAwareTestContext currentTestContext =
+ new ContainerAwareTestContext(testControl, this.testContext);
+
+ extensionContext.getTestMethod().ifPresent(method ->
+ {
+ try
+ {
+ currentTestContext.applyBeforeMethodConfig(method);
+ }
+ catch (Exception e)
+ {
+ throw ExceptionUtils.throwAsRuntimeException(e);
+ }
+ });
+
+ this.testContext = currentTestContext;
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception
+ {
+ try
+ {
+ if (this.testContext != null)
+ {
+ this.testContext.applyAfterMethodConfig();
+ }
+ }
+ finally
+ {
+ currentTestExtension.set(null);
+ currentTestExtension.remove();
+ }
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception
+ {
+ if (this.testContext == null)
+ {
+ TestControl testControl = extensionContext.getTestClass()
+ .map(cls -> cls.getAnnotation(TestControl.class)).orElse(null);
+
+ this.testContext = new ContainerAwareTestContext(testControl, null);
+
+ Class<? extends Handler> logHandlerClass = this.testContext.getLogHandlerClass();
+
+ if (!Handler.class.equals(logHandlerClass))
+ {
+ try
+ {
+ LOGGER.addHandler(logHandlerClass.newInstance());
+ }
+ catch (Exception e)
+ {
+ throw ExceptionUtils.throwAsRuntimeException(e);
+ }
+ }
+
+ this.statementDecoratorFactories = ServiceUtils.loadServiceImplementations(TestStatementDecoratorFactory.class);
+ Collections.sort(this.statementDecoratorFactories, new Comparator<TestStatementDecoratorFactory>()
+ {
+ @Override
+ public int compare(TestStatementDecoratorFactory f1, TestStatementDecoratorFactory f2)
+ {
+ return f1.getOrdinal() > f2.getOrdinal() ? 1 : -1;
+ }
+ });
+ }
+
+ this.testContext.applyBeforeClassConfig(extensionContext.getTestClass().orElseThrow());
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception
+ {
+ if (this.testContext != null)
+ {
+ this.testContext.applyAfterClassConfig();
+ }
+ }
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException
+ {
+ return false;
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException
+ {
+ return null;
+ }
+
+ private class ContainerAwareTestContext
+ {
+ private ContainerAwareTestContext parent;
+
+ private final ProjectStage projectStage;
+ private final TestControl testControl;
+
+ private ProjectStage previousProjectStage;
+
+ private boolean containerStarted = false;
+
+ private Stack<Class<? extends Annotation>> startedScopes = new Stack<Class<? extends Annotation>>();
+
+ private List<ExternalContainer> externalContainers;
+
+ ContainerAwareTestContext(TestControl testControl, ContainerAwareTestContext parent)
+ {
+ this.parent = parent;
+
+ Class<? extends ProjectStage> foundProjectStageClass;
+ if (testControl == null)
+ {
+ this.testControl = new TestControlLiteral();
+ if (parent != null)
+ {
+ foundProjectStageClass = parent.testControl.projectStage();
+ }
+ else
+ {
+ foundProjectStageClass = this.testControl.projectStage();
+ }
+ }
+ else
+ {
+ this.testControl = testControl;
+ foundProjectStageClass = this.testControl.projectStage();
+ }
+ this.projectStage = ProjectStage.valueOf(foundProjectStageClass.getSimpleName());
+
+ ProjectStageProducer.setProjectStage(this.projectStage);
+ }
+
+ boolean isContainerStarted()
+ {
+ return this.containerStarted || (this.parent != null && this.parent.isContainerStarted());
+ }
+
+ Class<? extends Handler> getLogHandlerClass()
+ {
+ return this.testControl.logHandler();
+ }
+
+ void applyBeforeClassConfig(Class<?> testClass)
+ {
+ CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+ if (!isContainerStarted())
+ {
+ System.setProperty("org.jboss.weld.environment.servlet.archive.isolation", "false");
+
+ container.boot(CdiTestSuiteExtension.getTestContainerConfig());
+ setContainerStarted();
+
+ bootExternalContainers(testClass);
+ }
+
+ List<Class<? extends Annotation>> restrictedScopes = new ArrayList<Class<? extends Annotation>>();
+
+ restrictedScopes.add(ApplicationScoped.class);
+ restrictedScopes.add(Singleton.class);
+
+ if (this.parent == null && this.testControl.getClass().equals(TestControlLiteral.class))
+ {
+ restrictedScopes.add(RequestScoped.class);
+ restrictedScopes.add(SessionScoped.class);
+ }
+
+ startScopes(container, testClass, null, restrictedScopes.toArray(new Class[restrictedScopes.size()]));
+ }
+
+ private void bootExternalContainers(Class testClass)
+ {
+ if (!this.testControl.startExternalContainers())
+ {
+ return;
+ }
+
+ if (this.externalContainers == null)
+ {
+ List<ExternalContainer> configuredExternalContainers =
+ ServiceUtils.loadServiceImplementations(ExternalContainer.class);
+ Collections.sort(configuredExternalContainers, new Comparator<ExternalContainer>()
+ {
+ @Override
+ public int compare(ExternalContainer ec1, ExternalContainer ec2)
+ {
+ return ec1.getOrdinal() > ec2.getOrdinal() ? 1 : -1;
+ }
+ });
+
+ this.externalContainers = new ArrayList<ExternalContainer>(configuredExternalContainers.size());
+
+ ExternalContainer externalContainerBean;
+ for (ExternalContainer externalContainer : configuredExternalContainers)
+ {
+ externalContainerBean = BeanProvider.getContextualReference(externalContainer.getClass(), true);
+
+ if (externalContainerBean != null)
+ {
+ this.externalContainers.add(externalContainerBean);
+ }
+ else
+ {
+ this.externalContainers.add(externalContainer);
+ }
+ }
+
+ for (ExternalContainer externalContainer : this.externalContainers)
+ {
+ try
+ {
+ if (externalContainer instanceof TestAware)
+ {
+ ((TestAware) externalContainer).setTestClass(testClass);
+ }
+ externalContainer.boot();
+ }
+ catch (RuntimeException e)
+ {
+ Logger.getLogger(CdiTestExtension.class.getName()).log(Level.WARNING,
+ "booting " + externalContainer.getClass().getName() + " failed", e);
+ }
+ }
+ }
+ }
+
+ void applyAfterClassConfig()
+ {
+ CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+ stopStartedScopes(container);
+
+ if (this.containerStarted)
+ {
+ if (CdiTestSuiteExtension.isStopContainerAllowed())
+ {
+ shutdownExternalContainers();
+
+ container.shutdown();
+ CdiTestSuiteExtension.setContainerStarted(false);
+ }
+ }
+ }
+
+ private void shutdownExternalContainers()
+ {
+ if (this.externalContainers == null)
+ {
+ return;
+ }
+
+ for (ExternalContainer externalContainer : this.externalContainers)
+ {
+ try
+ {
+ externalContainer.shutdown();
+ }
+ catch (RuntimeException e)
+ {
+ Logger.getLogger(CdiTestExtension.class.getName()).log(Level.WARNING,
+ "shutting down " + externalContainer.getClass().getName() + " failed", e);
+ }
+ }
+ }
+
+ void applyBeforeMethodConfig(Method testMethod)
+ {
+ this.previousProjectStage = ProjectStageProducer.getInstance().getProjectStage();
+ ProjectStageProducer.setProjectStage(this.projectStage);
+
+ setCurrentTestMethod(testMethod);
+ startScopes(CdiContainerLoader.getCdiContainer(), testMethod.getDeclaringClass(), testMethod);
+ }
+
+ void applyAfterMethodConfig()
+ {
+ try
+ {
+ stopStartedScopes(CdiContainerLoader.getCdiContainer());
+ }
+ finally
+ {
+ setCurrentTestMethod(null);
+ ProjectStageProducer.setProjectStage(previousProjectStage);
+ previousProjectStage = null;
+ }
+ }
+
+ void setContainerStarted()
+ {
+ this.containerStarted = true;
+ CdiTestSuiteExtension.setContainerStarted(true);
+ }
+
+ private void startScopes(CdiContainer container,
+ Class testClass,
+ Method testMethod,
+ Class<? extends Annotation>... restrictedScopes)
+ {
+ try
+ {
+ automaticScopeHandlingActive.set(true);
+
+ ContextControl contextControl = container.getContextControl();
+
+ List<Class<? extends Annotation>> scopeClasses = new ArrayList<Class<? extends Annotation>>();
+
+ Collections.addAll(scopeClasses, this.testControl.startScopes());
+
+ if (scopeClasses.isEmpty())
+ {
+ addScopesForDefaultBehavior(scopeClasses);
+ }
+ else
+ {
+ List<TestControlValidator> testControlValidatorList =
+ ServiceUtils.loadServiceImplementations(TestControlValidator.class);
+
+ for (TestControlValidator testControlValidator : testControlValidatorList)
+ {
+ if (testControlValidator instanceof TestAware)
+ {
+ if (testMethod != null)
+ {
+ ((TestAware) testControlValidator).setTestMethod(testMethod);
+ }
+ ((TestAware) testControlValidator).setTestClass(testClass);
+ }
+ try
+ {
+ testControlValidator.validate(this.testControl);
+ }
+ finally
+ {
+ if (testControlValidator instanceof TestAware)
+ {
+ ((TestAware) testControlValidator).setTestClass(null);
+ ((TestAware) testControlValidator).setTestMethod(null);
+ }
+ }
+ }
+ }
+
+ for (Class<? extends Annotation> scopeAnnotation : scopeClasses)
+ {
+ if (this.parent != null && this.parent.isScopeStarted(scopeAnnotation))
+ {
+ continue;
+ }
+
+ if (isRestrictedScope(scopeAnnotation, restrictedScopes))
+ {
+ continue;
+ }
+
+ try
+ {
+ contextControl.stopContext(scopeAnnotation);
+
+ contextControl.startContext(scopeAnnotation);
+ this.startedScopes.add(scopeAnnotation);
+
+ onScopeStarted(scopeAnnotation);
+ }
+ catch (RuntimeException e)
+ {
+ Logger logger = Logger.getLogger(CdiTestExtension.class.getName());
+ logger.setLevel(Level.SEVERE);
+ logger.log(Level.SEVERE, "failed to start scope @" + scopeAnnotation.getName(), e);
+ }
+ }
+ }
+ finally
+ {
+ automaticScopeHandlingActive.set(null);
+ automaticScopeHandlingActive.remove();
+ }
+ }
+
+ private void addScopesForDefaultBehavior(List<Class<? extends Annotation>> scopeClasses)
+ {
+ if (this.parent != null && !this.parent.isScopeStarted(RequestScoped.class))
+ {
+ if (!scopeClasses.contains(RequestScoped.class))
+ {
+ scopeClasses.add(RequestScoped.class);
+ }
+ }
+ if (this.parent != null && !this.parent.isScopeStarted(SessionScoped.class))
+ {
+ if (!scopeClasses.contains(SessionScoped.class))
+ {
+ scopeClasses.add(SessionScoped.class);
+ }
+ }
+ }
+
+ private boolean isRestrictedScope(Class<? extends Annotation> scopeAnnotation,
+ Class<? extends Annotation>[] restrictedScopes)
+ {
+ for (Class<? extends Annotation> restrictedScope : restrictedScopes)
+ {
+ if (scopeAnnotation.equals(restrictedScope))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isScopeStarted(Class<? extends Annotation> scopeAnnotation)
+ {
+ return this.startedScopes.contains(scopeAnnotation);
+ }
+
+ private void stopStartedScopes(CdiContainer container)
+ {
+ try
+ {
+ automaticScopeHandlingActive.set(true);
+
+ while (!this.startedScopes.empty())
+ {
+ Class<? extends Annotation> scopeAnnotation = this.startedScopes.pop();
+ try
+ {
+ container.getContextControl().stopContext(scopeAnnotation);
+ onScopeStopped(scopeAnnotation);
+ }
+ catch (RuntimeException e)
+ {
+ Logger logger = Logger.getLogger(CdiTestExtension.class.getName());
+ logger.setLevel(Level.SEVERE);
+ logger.log(Level.SEVERE, "failed to stop scope @" + scopeAnnotation.getName(), e);
+ }
+ }
+ }
+ finally
+ {
+ automaticScopeHandlingActive.remove();
+ automaticScopeHandlingActive.set(null);
+ }
+ }
+
+ private void onScopeStarted(Class<? extends Annotation> scopeClass)
+ {
+ List<ExternalContainer> externalContainerList = collectExternalContainers(this);
+
+ for (ExternalContainer externalContainer : externalContainerList)
+ {
+ externalContainer.startScope(scopeClass);
+ }
+ }
+
+ private void onScopeStopped(Class<? extends Annotation> scopeClass)
+ {
+ List<ExternalContainer> externalContainerList = collectExternalContainers(this);
+
+ for (ExternalContainer externalContainer : externalContainerList)
+ {
+ externalContainer.stopScope(scopeClass);
+ }
+ }
+
+ private List<ExternalContainer> collectExternalContainers(ContainerAwareTestContext testContext)
+ {
+ List<ExternalContainer> result = new ArrayList<ExternalContainer>();
+
+ if (testContext.externalContainers != null)
+ {
+ result.addAll(testContext.externalContainers);
+ }
+
+ if (testContext.parent != null)
+ {
+ result.addAll(collectExternalContainers(testContext.parent));
+ }
+ return result;
+ }
+
+ private void setCurrentTestMethod(Method testMethod)
+ {
+ List<ExternalContainer> externalContainerList = collectExternalContainers(this);
+
+ for (ExternalContainer externalContainer : externalContainerList)
+ {
+ if (externalContainer instanceof TestAware)
+ {
+ try
+ {
+ ((TestAware) externalContainer).setTestMethod(testMethod);
+ }
+ catch (Throwable t)
+ {
+ throw new RuntimeException(t.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ public static Boolean isAutomaticScopeHandlingActive()
+ {
+ return automaticScopeHandlingActive.get();
+ }
+
+ public static List<ExternalContainer> getActiveExternalContainers()
+ {
+ CdiTestExtension cdiTestExtension = currentTestExtension.get();
+
+ if (cdiTestExtension == null ||
+ cdiTestExtension.testContext == null ||
+ cdiTestExtension.testContext.externalContainers == null)
+ {
+ return Collections.emptyList();
+ }
+
+ return Collections.unmodifiableList(cdiTestExtension.testContext.externalContainers);
+ }
+
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java
new file mode 100644
index 000000000..18b305820
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/CdiTestSuiteExtension.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.cdise.api.CdiContainer;
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.core.api.config.ConfigResolver;
+import org.apache.deltaspike.core.api.config.PropertyLoader;
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.core.spi.config.ConfigSource;
+import org.apache.deltaspike.core.util.ClassDeactivationUtils;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import jakarta.inject.Named;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class CdiTestSuiteExtension implements BeforeAllCallback, AfterAllCallback
+{
+ public static final String CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY =
+ "deltaspike.testcontrol5.test-container.config-file";
+
+ public static final String DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME =
+ "META-INF/apache-deltaspike_test-container";
+
+ private static final boolean STOP_CONTAINER;
+
+ private static final ThreadLocal<Boolean> IS_CDI_TEST_RUNNER_EXECUTION = new ThreadLocal<Boolean>();
+
+ private static volatile boolean containerStarted;
+
+ private Class<?> testSuiteClass;
+
+ static
+ {
+ STOP_CONTAINER = TestBaseConfig.ContainerIntegration.STOP_CONTAINER;
+ }
+
+ public CdiTestSuiteExtension()
+ {
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception
+ {
+ this.testSuiteClass = extensionContext.getTestClass()
+ .orElseThrow(() -> new IllegalStateException("no test-suite class found"));
+
+ CdiContainer container = CdiContainerLoader.getCdiContainer();
+
+ if (!containerStarted)
+ {
+ applyTestSpecificMetaData(testSuiteClass);
+
+ container.boot(getTestContainerConfig());
+ containerStarted = true;
+ }
+
+ try
+ {
+ // Tests will be executed by JUnit 5
+ }
+ finally
+ {
+ if (STOP_CONTAINER)
+ {
+ container.shutdown();
+ containerStarted = false;
+ }
+ }
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception
+ {
+ // Cleanup if needed
+ }
+
+ public static boolean isContainerStarted()
+ {
+ return containerStarted;
+ }
+
+ static Boolean isStopContainerAllowed()
+ {
+ return STOP_CONTAINER;
+ }
+
+ static ThreadLocal<Boolean> getCdiTestRunnerExecutionRef()
+ {
+ return IS_CDI_TEST_RUNNER_EXECUTION;
+ }
+
+ static void setContainerStarted(boolean containerStarted)
+ {
+ CdiTestSuiteExtension.containerStarted = containerStarted;
+ }
+
+ public static Properties getTestContainerConfig()
+ {
+ String cdiTestRunnerConfig = ConfigResolver.getProjectStageAwarePropertyValue(
+ CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY, DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME);
+ return PropertyLoader.getProperties(cdiTestRunnerConfig);
+ }
+
+ static void applyTestSpecificMetaData(Class<?> currentAnnotationSource)
+ {
+ TestControl testControl = currentAnnotationSource.getAnnotation(TestControl.class);
+ String activeAlternativeLabel = checkForLabeledAlternativeConfig(testControl);
+
+ initTestEnvConfig(currentAnnotationSource, activeAlternativeLabel, testControl);
+ }
+
+ private static String checkForLabeledAlternativeConfig(TestControl testControl)
+ {
+ String activeAlternativeLabel = "";
+
+ if (testControl != null)
+ {
+ Class<? extends TestControl.Label> activeTypedAlternativeLabel =
+ testControl.activeAlternativeLabel();
+
+ if (!TestControl.Label.class.equals(activeTypedAlternativeLabel))
+ {
+ Named labelName = activeTypedAlternativeLabel.getAnnotation(Named.class);
+
+ if (labelName != null)
+ {
+ activeAlternativeLabel = labelName.value();
+ }
+ else
+ {
+ String labelClassName = activeTypedAlternativeLabel.getSimpleName();
+ activeAlternativeLabel = labelClassName.substring(0, 1).toLowerCase();
+
+ if (labelClassName.length() > 1)
+ {
+ activeAlternativeLabel += labelClassName.substring(1);
+ }
+ }
+ }
+ }
+ return activeAlternativeLabel;
+ }
+
+ private static void initTestEnvConfig(Class<?> testClass, String activeAlternativeLabel, TestControl testControl)
+ {
+ if (ClassDeactivationUtils.isActivated(TestConfigSource.class))
+ {
+ TestConfigSource testConfigSource = null;
+
+ for (ConfigSource configSource : ConfigResolver.getConfigSources())
+ {
+ if (configSource instanceof TestConfigSource)
+ {
+ testConfigSource = (TestConfigSource) configSource;
+ }
+ }
+
+ if (testConfigSource == null)
+ {
+ testConfigSource = new TestConfigSource();
+ ConfigResolver.addConfigSources(Arrays.<ConfigSource>asList(testConfigSource));
+ }
+
+ testConfigSource.getProperties().put("activeAlternativeLabel", activeAlternativeLabel);
+
+ testConfigSource.getProperties().put("activeAlternativeLabelSource", testClass.getName());
+
+ if (testControl != null)
+ {
+ testConfigSource.getProperties().put(TestControl.class.getName(), testClass.getName());
+ testConfigSource.getProperties().put(ClassFilter.class.getName(), testControl.classFilter().getName());
+ }
+ else
+ {
+ testConfigSource.getProperties().put(TestControl.class.getName(), TestControl.class.getName());
+ testConfigSource.getProperties().put(ClassFilter.class.getName(), ClassFilter.class.getName());
+ }
+ }
+ else
+ {
+ System.setProperty("activeAlternativeLabel", activeAlternativeLabel);
+
+ System.setProperty("activeAlternativeLabelSource", testClass.getName());
+
+ if (testControl != null)
+ {
+ System.setProperty(TestControl.class.getName(), testClass.getName());
+ System.setProperty(ClassFilter.class.getName(), testControl.classFilter().getName());
+ }
+ else
+ {
+ System.setProperty(TestControl.class.getName(), TestControl.class.getName());
+ System.setProperty(ClassFilter.class.getName(), ClassFilter.class.getName());
+ }
+ }
+ }
+
+ public static class TestConfigSource implements ConfigSource, Deactivatable
+ {
+ private Map<String, String> testConfig = new ConcurrentHashMap<String, String>();
+
+ @Override
+ public int getOrdinal()
+ {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public Map<String, String> getProperties()
+ {
+ return testConfig;
+ }
+
+ @Override
+ public String getPropertyValue(String key)
+ {
+ return testConfig.get(key);
+ }
+
+ @Override
+ public String getConfigName()
+ {
+ return "ds-test5-config";
+ }
+
+ @Override
+ public boolean isScannable()
+ {
+ return true;
+ }
+ }
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java
new file mode 100644
index 000000000..0928e3fae
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/junit/TestBaseConfig.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.junit;
+
+import org.apache.deltaspike.core.api.config.ConfigResolver;
+import org.apache.deltaspike.core.api.config.base.DeltaSpikeBaseConfig;
+
+public interface TestBaseConfig extends DeltaSpikeBaseConfig
+{
+ interface ContainerIntegration
+ {
+ //default is false to improve the compatibility with @Before and @After
+ Boolean USE_TEST_CLASS_AS_CDI_BEAN = ConfigResolver.resolve("deltaspike.testcontrol5.use_test_class_as_cdi_bean")
+ .as(Boolean.class)
+ .withCurrentProjectStage(true)
+ .withDefault(Boolean.FALSE)
+ .getValue();
+
+ Boolean STOP_CONTAINER = ConfigResolver.resolve("deltaspike.testcontrol5.stop_container")
+ .as(Boolean.class)
+ .withCurrentProjectStage(true)
+ .withDefault(Boolean.TRUE)
+ .getValue();
+ }
+
+ interface MockIntegration
+ {
+ String ALLOW_MOCKED_BEANS_KEY = "deltaspike.testcontrol5.mock-support.allow_mocked_beans";
+ String ALLOW_MOCKED_PRODUCERS_KEY = "deltaspike.testcontrol5.mock-support.allow_mocked_producers";
+ String ALLOW_MANUAL_INJECTION_POINT_MANIPULATION_KEY =
+ "deltaspike.testcontrol5.mock-support.allow_manual_injection-point_manipulation";
+
+ Boolean ALLOW_MOCKED_BEANS = ConfigResolver.resolve(ALLOW_MOCKED_BEANS_KEY)
+ .as(Boolean.class)
+ .withCurrentProjectStage(true)
+ .withDefault(Boolean.FALSE)
+ .getValue();
+
+ Boolean ALLOW_MOCKED_PRODUCERS = ConfigResolver.resolve(ALLOW_MOCKED_PRODUCERS_KEY)
+ .as(Boolean.class)
+ .withCurrentProjectStage(true)
+ .withDefault(Boolean.FALSE)
+ .getValue();
+
+ //if enabled it's possible to change the value of injection-points after the injection-process and
+ //before test-execution. that allows to replace injection-points (e.g. with a mock) conditionally
+ //via a test-rule or @Before
+ Boolean ALLOW_MANUAL_INJECTION_POINT_MANIPULATION =
+ ConfigResolver.resolve(ALLOW_MANUAL_INJECTION_POINT_MANIPULATION_KEY)
+ .as(Boolean.class)
+ .withCurrentProjectStage(true)
+ .withDefault(Boolean.FALSE)
+ .getValue();
+ }
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java
new file mode 100644
index 000000000..a5edd44e2
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/literal/TestControlLiteral.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.literal;
+
+import org.apache.deltaspike.core.spi.filter.ClassFilter;
+import org.apache.deltaspike.core.api.projectstage.ProjectStage;
+import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+
+import jakarta.enterprise.util.AnnotationLiteral;
+import java.lang.annotation.Annotation;
+import java.util.logging.Handler;
+
+public class TestControlLiteral extends AnnotationLiteral<TestControl> implements TestControl
+{
+ private static final long serialVersionUID = 6684011035751678259L;
+
+ private final TestControl defaultInstance;
+
+ public TestControlLiteral()
+ {
+ this.defaultInstance = AnnotationInstanceProvider.of(TestControl.class);
+ }
+
+ @Override
+ public Class<? extends Annotation>[] startScopes()
+ {
+ return defaultInstance.startScopes();
+ }
+
+ @Override
+ public Class<? extends ProjectStage> projectStage()
+ {
+ return defaultInstance.projectStage();
+ }
+
+ @Override
+ public Class<? extends Handler> logHandler()
+ {
+ return defaultInstance.logHandler();
+ }
+
+ @Override
+ public boolean startExternalContainers()
+ {
+ return defaultInstance.startExternalContainers();
+ }
+
+ @Override
+ public Class<? extends Label> activeAlternativeLabel()
+ {
+ return defaultInstance.activeAlternativeLabel();
+ }
+
+ @Override
+ public Class<? extends ClassFilter> classFilter()
+ {
+ return defaultInstance.classFilter();
+ }
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java
new file mode 100644
index 000000000..7dff1f36d
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/ApplicationMockManager.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.mock;
+
+import java.lang.annotation.Annotation;
+
+public interface ApplicationMockManager
+{
+ void addMock(Object mockInstance, Annotation... qualifiers);
+
+ <T> T getMock(Class<T> beanClass, Annotation... qualifiers);
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java
new file mode 100644
index 000000000..c41178eb8
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/DynamicMockManager.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.mock;
+
+public interface DynamicMockManager extends ApplicationMockManager
+{
+ void reset();
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.java
new file mode 100644
index 000000000..ff06c76f6
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/api/mock/TypedMock.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.api.mock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({ FIELD, METHOD, TYPE })
+@Retention(RUNTIME)
+public @interface TypedMock
+{
+ Class<?>[] value() default { };
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.java
new file mode 100644
index 000000000..189482b14
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/ExternalContainer.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.spi;
+
+import java.lang.annotation.Annotation;
+
+public interface ExternalContainer
+{
+ void boot();
+
+ void shutdown();
+
+ int getOrdinal();
+
+ void startScope(Class<? extends Annotation> scopeClass);
+
+ void stopScope(Class<? extends Annotation> scopeClass);
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java
new file mode 100644
index 000000000..19de3b912
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestAware.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.spi;
+
+import java.lang.reflect.Method;
+
+public interface TestAware
+{
+ void setTestClass(Class testClass);
+
+ void setTestMethod(Method testMethod);
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java
new file mode 100644
index 000000000..0475047a5
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/TestControlValidator.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.spi;
+
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+
+public interface TestControlValidator extends Deactivatable
+{
+ void validate(TestControl testControl);
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java
new file mode 100644
index 000000000..ba68a53a3
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/junit/TestStatementDecoratorFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.spi.junit;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public interface TestStatementDecoratorFactory
+{
+ ExtensionContext createContext(ExtensionContext extensionContext);
+
+ int getOrdinal();
+}
diff --git a/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java
new file mode 100644
index 000000000..5b804f093
--- /dev/null
+++ b/deltaspike/modules/test-control5/api/src/main/java/org/apache/deltaspike/testcontrol5/spi/mock/MockFilter.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.spi.mock;
+
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+
+import jakarta.enterprise.inject.spi.Annotated;
+import jakarta.enterprise.inject.spi.BeanManager;
+
+public interface MockFilter extends Deactivatable
+{
+ boolean isMockedImplementationSupported(BeanManager beanManager, Annotated annotated);
+}
diff --git a/deltaspike/modules/test-control5/impl/pom.xml b/deltaspike/modules/test-control5/impl/pom.xml
new file mode 100644
index 000000000..a9cbed09c
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>test-control5-module-project</artifactId>
+ <version>2.0.2-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>deltaspike-test-control5-module-impl</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Apache DeltaSpike Test-Control5-Module Impl (JUnit 5)</name>
+
+ <properties>
+ <deltaspike.osgi.export.pkg>
+ org.apache.deltaspike.testcontrol5.impl.*
+ </deltaspike.osgi.export.pkg>
+ <deltaspike.osgi.import>
+ !org.apache.deltaspike.testcontrol5.impl.*,
+ *
+ </deltaspike.osgi.import>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>deltaspike-test-control5-module-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.deltaspike.cdictrl</groupId>
+ <artifactId>deltaspike-cdictrl-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jpa_2.2_spec</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>${junit5.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>jakarta.el</groupId>
+ <artifactId>jakarta.el-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.deltaspike.core</groupId>
+ <artifactId>deltaspike-core-impl</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+
+
+ <profiles>
+ <profile>
+ <id>OWB</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.openwebbeans</groupId>
+ <artifactId>openwebbeans-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.openwebbeans</groupId>
+ <artifactId>openwebbeans-spi</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.deltaspike.cdictrl</groupId>
+ <artifactId>deltaspike-cdictrl-owb</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>Weld</id>
+ <dependencies>
+ <!-- Adding a test dependency needed for Weld -->
+ <dependency>
+ <groupId>org.apache.deltaspike.cdictrl</groupId>
+ <artifactId>deltaspike-cdictrl-weld</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- The remaining bits of this profile are located under deltaspike/parent/code/pom.xml -->
+ </dependencies>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java
new file mode 100644
index 000000000..d486d27b5
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/AbstractMockManager.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.junit.TestBaseConfig;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+import org.apache.deltaspike.testcontrol5.api.mock.TypedMock;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractMockManager implements DynamicMockManager
+{
+ private Map<BeanCacheKey, Object> registeredMocks = new HashMap<BeanCacheKey, Object>();
+
+ @Override
+ public void addMock(Object mockInstance, Annotation... qualifiers)
+ {
+ if (!TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS &&
+ !TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS)
+ {
+ throw new IllegalStateException("The support for mocked CDI-Beans is disabled " +
+ "due to a reduced portability across different CDI-implementations. " +
+ "Please set '" + TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS_KEY + "' and/or '" +
+ TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS_KEY + "' to 'true' " +
+ "(in 'META-INF/apache-deltaspike.properties') on your test-classpath.");
+ }
+
+ Class<?> mockClass = mockInstance.getClass();
+ Class<?> beanClass = mockClass.getSuperclass();
+
+ if (beanClass == null)
+ {
+ beanClass = mockClass;
+ }
+ if (Object.class.equals(beanClass))
+ {
+ throw new IllegalArgumentException(mockInstance.getClass().getName() +
+ " isn't a supported approach for mocking -> please extend from the original class.");
+ }
+
+ TypedMock typedMock = mockClass.getAnnotation(TypedMock.class);
+
+ if (typedMock == null)
+ {
+ typedMock = beanClass.getAnnotation(TypedMock.class);
+ }
+
+ Class[] specifiedTypes = null;
+
+ if (typedMock != null)
+ {
+ specifiedTypes = typedMock.value();
+ }
+ else
+ {
+ Typed typed = mockClass.getAnnotation(Typed.class);
+
+ if (typed == null || typed.value().length == 0)
+ {
+ typed = beanClass.getAnnotation(Typed.class);
+ }
+
+ if (typed != null && typed.value().length > 0)
+ {
+ specifiedTypes = typed.value();
+ }
+ }
+
+ if (specifiedTypes != null)
+ {
+ for (Class typedClass : specifiedTypes)
+ {
+ this.registeredMocks.put(new BeanCacheKey(typedClass, qualifiers), mockInstance);
+ }
+ }
+ else
+ {
+ this.registeredMocks.put(new BeanCacheKey(beanClass, qualifiers), mockInstance);
+ }
+ }
+
+ @Override
+ public <T> T getMock(Class<T> beanClass, Annotation... qualifiers)
+ {
+ return (T)this.registeredMocks.get(new BeanCacheKey(beanClass, qualifiers));
+ }
+
+ @Override
+ public void reset()
+ {
+ this.registeredMocks.clear();
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java
new file mode 100644
index 000000000..0461a4d3f
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/BeanCacheKey.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.util.ReflectionUtils;
+
+import jakarta.enterprise.util.Nonbinding;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class BeanCacheKey
+{
+ private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new AnnotationComparator();
+
+ private final Type type;
+ private final Annotation qualifier;
+ private final Annotation qualifiers[];
+ private final int hashCode;
+
+ public BeanCacheKey(Type type, Annotation... qualifiers)
+ {
+ this.type = type;
+ final int length = qualifiers != null ? qualifiers.length : 0;
+ if (length == 0)
+ {
+ qualifier = null;
+ this.qualifiers = null;
+ }
+ else if (length == 1)
+ {
+ qualifier = qualifiers[0];
+ this.qualifiers = null;
+ }
+ else
+ {
+ qualifier = null;
+ this.qualifiers = new Annotation[length];
+ System.arraycopy(qualifiers, 0, this.qualifiers, 0, length);
+ Arrays.sort(this.qualifiers, ANNOTATION_COMPARATOR);
+ }
+
+ hashCode = computeHashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+
+ BeanCacheKey cacheKey = (BeanCacheKey) o;
+
+ if (!type.equals(cacheKey.type))
+ {
+ return false;
+ }
+ if (qualifier != null ? !qualifierEquals(qualifier, cacheKey.qualifier) : cacheKey.qualifier != null)
+ {
+ return false;
+ }
+ if (!qualifierArrayEquals(qualifiers, cacheKey.qualifiers))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean qualifierArrayEquals(Annotation[] qualifiers1, Annotation[] qualifiers2)
+ {
+ if (qualifiers1 == qualifiers2)
+ {
+ return true;
+ }
+ else if (qualifiers1 == null || qualifiers2 == null)
+ {
+ return false;
+ }
+ if (qualifiers1.length != qualifiers2.length)
+ {
+ return false;
+ }
+ for (int i = 0; i < qualifiers1.length; i++)
+ {
+ Annotation a1 = qualifiers1[i];
+ Annotation a2 = qualifiers2[i];
+ if (a1 == null ? a2 != null : !qualifierEquals(a1, a2))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ private int computeHashCode()
+ {
+ int computedHashCode = 31 * ReflectionUtils.calculateHashCodeOfType(type);
+ if (qualifier != null)
+ {
+ computedHashCode = 31 * computedHashCode + getQualifierHashCode(qualifier);
+ }
+ if (qualifiers != null)
+ {
+ for (int i = 0; i < qualifiers.length; i++)
+ {
+ computedHashCode = 31 * computedHashCode + getQualifierHashCode(qualifiers[i]);
+ }
+ }
+ return computedHashCode;
+ }
+
+ private int getQualifierHashCode(Annotation a)
+ {
+ return ReflectionUtils.calculateHashCodeOfAnnotation(a, true);
+ }
+
+ private boolean qualifierEquals(Annotation qualifier1, Annotation qualifier2)
+ {
+ return ANNOTATION_COMPARATOR.compare(qualifier1, qualifier2) == 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "BeanCacheKey{" + "type=" + type + ", qualifiers="
+ + (qualifiers == null ? qualifier : Arrays.asList(qualifiers)) + ", hashCode=" + hashCode + '}';
+ }
+
+ private static class AnnotationComparator implements Comparator<Annotation>
+ {
+ @Override
+ public int compare(Annotation annotation1, Annotation annotation2)
+ {
+ final Class<? extends Annotation> type1 = annotation1.annotationType();
+ final Class<? extends Annotation> type2 = annotation2.annotationType();
+ final int temp = type1.getName().compareTo(type2.getName());
+ if (temp != 0)
+ {
+ return temp;
+ }
+ final Method[] member1 = type1.getDeclaredMethods();
+ final Method[] member2 = type2.getDeclaredMethods();
+
+ int i = 0;
+ int j = 0;
+ final int length1 = member1.length;
+ final int length2 = member2.length;
+
+ for (;; i++, j++)
+ {
+ while (i < length1 && member1[i].isAnnotationPresent(Nonbinding.class))
+ {
+ i++;
+ }
+ while (j < length2 && member2[j].isAnnotationPresent(Nonbinding.class))
+ {
+ j++;
+ }
+ if (i >= length1 && j >= length2)
+ {
+ return 0;
+ }
+ else if (i >= length1)
+ {
+ return 1;
+ }
+ else if (j >= length2)
+ {
+ return -1;
+ }
+ else
+ {
+ int c = member1[i].getName().compareTo(member2[j].getName());
+ if (c != 0)
+ {
+ return c;
+ }
+ final Object value1 = ReflectionUtils.invokeMethod(annotation1, member1[i], Object.class, true);
+ final Object value2 = ReflectionUtils.invokeMethod(annotation2, member2[j], Object.class, true);
+ assert value1.getClass().equals(value2.getClass());
+
+ if (value1 instanceof Comparable)
+ {
+ c = ((Comparable)value1).compareTo(value2);
+ if (c != 0)
+ {
+ return c;
+ }
+ }
+ else if (value1.getClass().isArray())
+ {
+ c = value1.getClass().getComponentType().getName()
+ .compareTo(value2.getClass().getComponentType().getName());
+ if (c != 0)
+ {
+ return c;
+ }
+
+ final int length = Array.getLength(value1);
+ c = length - Array.getLength(value2);
+ if (c != 0)
+ {
+ return c;
+ }
+ for (int k = 0; k < length; k++)
+ {
+ c = ((Comparable)Array.get(value1, k)).compareTo(Array.get(value2, k));
+ if (c != 0)
+ {
+ return c;
+ }
+ }
+
+ }
+ else if (value1 instanceof Class)
+ {
+
+ c = ((Class)value1).getName().compareTo(((Class) value2).getName());
+ if (c != 0)
+ {
+ return c;
+ }
+ }
+ else
+ {
+ assert false;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java
new file mode 100644
index 000000000..14044ad5c
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/DefaultMockFilter.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.testcontrol5.api.junit.TestBaseConfig;
+import org.apache.deltaspike.testcontrol5.spi.mock.MockFilter;
+
+import jakarta.enterprise.inject.Produces;
+import jakarta.enterprise.inject.spi.Annotated;
+import jakarta.enterprise.inject.spi.AnnotatedField;
+import jakarta.enterprise.inject.spi.AnnotatedMember;
+import jakarta.enterprise.inject.spi.AnnotatedMethod;
+import jakarta.enterprise.inject.spi.AnnotatedType;
+import jakarta.enterprise.inject.spi.BeanManager;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+public class DefaultMockFilter implements MockFilter
+{
+ private static final Logger LOG = Logger.getLogger(DefaultMockFilter.class.getName());
+
+ private static final String DS_BASE_PACKAGE = "org.apache.deltaspike.";
+ private static final String JAVA_BASE_PACKAGE = "java.";
+ private static final String JAVAX_BASE_PACKAGE = "javax.";
+ private static final String JAKARTA_BASE_PACKAGE = "jakarta.";
+ private static final String EJB_BASE_PACKAGE = "javax.ejb.";
+ private static final String OWB_BASE_PACKAGE = "org.apache.webbeans.";
+ private static final String WELD_BASE_PACKAGE = "org.jboss.weld.";
+
+ @Override
+ public boolean isMockedImplementationSupported(BeanManager beanManager, Annotated annotated)
+ {
+ if (!isMockSupportEnabled(annotated))
+ {
+ return false;
+ }
+
+ Class origin = null;
+ if (annotated instanceof AnnotatedType)
+ {
+ origin = ((AnnotatedType)annotated).getJavaClass();
+ Set<Annotation> annotations = new HashSet<Annotation>();
+ annotations.addAll(annotated.getAnnotations());
+
+ for (AnnotatedMethod annotatedMethod :
+ (Set<jakarta.enterprise.inject.spi.AnnotatedMethod>)((AnnotatedType) annotated).getMethods())
+ {
+ annotations.addAll(annotatedMethod.getAnnotations());
+ }
+
+ if (isEjbOrAnnotatedTypeWithInterceptorAnnotation(
+ beanManager, annotations, origin.getName()))
+ {
+ return false;
+ }
+ }
+ else if (annotated instanceof AnnotatedMember)
+ {
+ Member member = ((AnnotatedMember)annotated).getJavaMember();
+ origin = member.getDeclaringClass();
+ if (isEjbOrAnnotatedTypeWithInterceptorAnnotation(
+ beanManager, annotated.getAnnotations(), member.toString()))
+ {
+ return false;
+ }
+ }
+
+ if (origin != null && origin.getPackage() == null)
+ {
+ LOG.warning("Please don't use the default-package for " + origin.getName());
+ return true;
+ }
+
+ return origin != null && !isInternalPackage(origin.getPackage().getName());
+ }
+
+ protected boolean isMockSupportEnabled(Annotated annotated)
+ {
+ if ((annotated instanceof AnnotatedMethod || annotated instanceof AnnotatedField) &&
+ annotated.getAnnotation(Produces.class) != null)
+ {
+ return TestBaseConfig.MockIntegration.ALLOW_MOCKED_PRODUCERS;
+ }
+ else
+ {
+ return TestBaseConfig.MockIntegration.ALLOW_MOCKED_BEANS;
+ }
+ }
+
+ protected boolean isEjbOrAnnotatedTypeWithInterceptorAnnotation(BeanManager beanManager,
+ Set<Annotation> annotations,
+ String origin)
+ {
+ for (Annotation annotation : annotations)
+ {
+ if (annotation.annotationType().getName().startsWith(EJB_BASE_PACKAGE))
+ {
+ return true;
+ }
+
+ if (isStandardAnnotation(annotation))
+ {
+ continue;
+ }
+
+ if (beanManager.isInterceptorBinding(annotation.annotationType()) ||
+ (beanManager.isStereotype(annotation.annotationType()) &&
+ isStereotypeWithInterceptor(annotation, beanManager)))
+ {
+ LOG.warning("Skip mocking intercepted bean " + origin);
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean isStereotypeWithInterceptor(Annotation stereotypeAnnotation, BeanManager beanManager)
+ {
+ for (Annotation annotation : stereotypeAnnotation.annotationType().getAnnotations())
+ {
+ if (isStandardAnnotation(annotation))
+ {
+ continue;
+ }
+
+ if (beanManager.isInterceptorBinding(annotation.annotationType()) ||
+ isStereotypeWithInterceptor(annotation, beanManager))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected boolean isStandardAnnotation(Annotation annotation)
+ {
+ return annotation.annotationType().getName().startsWith(JAVA_BASE_PACKAGE) ||
+ annotation.annotationType().getName().startsWith(JAVAX_BASE_PACKAGE) ||
+ annotation.annotationType().getName().startsWith(JAKARTA_BASE_PACKAGE);
+ }
+
+ protected boolean isInternalPackage(String packageName)
+ {
+ return packageName.startsWith(OWB_BASE_PACKAGE) || packageName.startsWith(WELD_BASE_PACKAGE) ||
+ isDeltaSpikePackage(packageName);
+ }
+
+ protected boolean isDeltaSpikePackage(String packageName)
+ {
+ return packageName.startsWith(DS_BASE_PACKAGE);
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java
new file mode 100644
index 000000000..c7e5246cf
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareInjectionTargetWrapper.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.InjectionPoint;
+import jakarta.enterprise.inject.spi.InjectionTarget;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+public class MockAwareInjectionTargetWrapper<T> implements InjectionTarget<T>
+{
+ private BeanManager beanManager;
+ private final InjectionTarget<T> wrapped;
+ private final List<Type> beanTypes;
+ private final List<Annotation> qualifiers;
+
+ public MockAwareInjectionTargetWrapper(BeanManager beanManager,
+ InjectionTarget<T> wrapped,
+ List<Type> beanTypes,
+ List<Annotation> qualifiers)
+ {
+ this.beanManager = beanManager;
+ this.wrapped = wrapped;
+ this.beanTypes = beanTypes;
+ this.qualifiers = qualifiers;
+ }
+
+ @Override
+ public T produce(CreationalContext<T> creationalContext)
+ {
+ DynamicMockManager mockManager =
+ BeanProvider.getContextualReference(this.beanManager, DynamicMockManager.class, false);
+
+ for (Type beanType : this.beanTypes)
+ {
+ Object mockInstance = mockManager.getMock(
+ (Class)beanType, this.qualifiers.toArray(new Annotation[this.qualifiers.size()]));
+
+ if (mockInstance != null)
+ {
+ return (T)mockInstance;
+ }
+ }
+ return wrapped.produce(creationalContext);
+ }
+
+ @Override
+ public void inject(T instance, CreationalContext<T> ctx)
+ {
+ wrapped.inject(instance, ctx);
+ }
+
+ @Override
+ public void postConstruct(T instance)
+ {
+ wrapped.postConstruct(instance);
+ }
+
+ @Override
+ public void preDestroy(T instance)
+ {
+ wrapped.preDestroy(instance);
+ }
+
+ @Override
+ public void dispose(T instance)
+ {
+ wrapped.dispose(instance);
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints()
+ {
+ return wrapped.getInjectionPoints();
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java
new file mode 100644
index 000000000..0b4ec4653
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockAwareProducerWrapper.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import org.apache.deltaspike.core.api.provider.BeanProvider;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.InjectionPoint;
+import jakarta.enterprise.inject.spi.Producer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+public class MockAwareProducerWrapper<T> implements Producer<T>
+{
+ private final BeanManager beanManager;
+ private final Producer<T> wrapped;
+ private final List<Type> beanTypes;
+ private final List<Annotation> qualifiers;
+
+ public MockAwareProducerWrapper(BeanManager beanManager,
+ Producer<T> wrapped,
+ List<Type> beanTypes,
+ List<Annotation> qualifiers)
+ {
+ this.beanManager = beanManager;
+ this.wrapped = wrapped;
+ this.beanTypes = beanTypes;
+ this.qualifiers = qualifiers;
+ }
+
+ @Override
+ public T produce(CreationalContext<T> creationalContext)
+ {
+ DynamicMockManager mockManager =
+ BeanProvider.getContextualReference(this.beanManager, DynamicMockManager.class, false);
+
+ for (Type beanType : this.beanTypes)
+ {
+ Object mockInstance = mockManager.getMock(
+ (Class)beanType, this.qualifiers.toArray(new Annotation[this.qualifiers.size()]));
+
+ if (mockInstance != null)
+ {
+ return (T)mockInstance;
+ }
+ }
+
+ return wrapped.produce(creationalContext);
+ }
+
+ @Override
+ public void dispose(T instance)
+ {
+ wrapped.dispose(instance);
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints()
+ {
+ return wrapped.getInjectionPoints();
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java
new file mode 100644
index 000000000..b94d29604
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/MockExtension.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.core.spi.activation.Deactivatable;
+import org.apache.deltaspike.core.util.ClassDeactivationUtils;
+import org.apache.deltaspike.core.util.ServiceUtils;
+import org.apache.deltaspike.testcontrol5.spi.mock.MockFilter;
+
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.inject.spi.AnnotatedMember;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
+import jakarta.enterprise.inject.spi.Extension;
+import jakarta.enterprise.inject.spi.InjectionTarget;
+import jakarta.enterprise.inject.spi.ProcessInjectionTarget;
+import jakarta.enterprise.inject.spi.ProcessProducer;
+import jakarta.enterprise.inject.spi.Producer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MockExtension implements Extension, Deactivatable
+{
+ private Boolean isActivated = true;
+ private List<MockFilter> mockFilters;
+
+ protected void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery)
+ {
+ isActivated = ClassDeactivationUtils.isActivated(getClass());
+ mockFilters = ServiceUtils.loadServiceImplementations(MockFilter.class);
+ }
+
+ public <X> void onProcessInjectionTarget(@Observes ProcessInjectionTarget<X> processInjectionTarget,
+ BeanManager beanManager)
+ {
+ if (!isActivated)
+ {
+ return;
+ }
+
+ for (MockFilter mockFilter : mockFilters)
+ {
+ if (!mockFilter.isMockedImplementationSupported(beanManager, processInjectionTarget.getAnnotatedType()))
+ {
+ return;
+ }
+ }
+
+ List<Annotation> qualifiers = new ArrayList<Annotation>();
+ for (Annotation annotation : processInjectionTarget.getAnnotatedType().getAnnotations())
+ {
+ if (beanManager.isQualifier(annotation.annotationType()))
+ {
+ qualifiers.add(annotation);
+ }
+ }
+
+ Typed typed = processInjectionTarget.getAnnotatedType().getAnnotation(Typed.class);
+
+ List<Type> foundTypes = new ArrayList<>();
+ if (typed != null)
+ {
+ Collections.addAll(foundTypes, typed.value());
+ }
+ else
+ {
+ foundTypes.addAll(extractTypes(processInjectionTarget.getAnnotatedType().getJavaClass()));
+ }
+
+ if (foundTypes.isEmpty())
+ {
+ return;
+ }
+
+ final InjectionTarget<X> originalInjectionTarget = processInjectionTarget.getInjectionTarget();
+ processInjectionTarget.setInjectionTarget(new MockAwareInjectionTargetWrapper<X>(
+ beanManager, originalInjectionTarget, foundTypes, qualifiers));
+ }
+
+ public <X, T> void onProcessProducer(@Observes ProcessProducer<X, T> processProducer, BeanManager beanManager)
+ {
+ if (!isActivated)
+ {
+ return;
+ }
+
+ for (MockFilter mockFilter : mockFilters)
+ {
+ if (!mockFilter.isMockedImplementationSupported(beanManager, processProducer.getAnnotatedMember()))
+ {
+ return;
+ }
+ }
+
+ final Producer<T> originalProducer = processProducer.getProducer();
+ AnnotatedMember<X> annotatedMember = processProducer.getAnnotatedMember();
+ List<Annotation> qualifiers = new ArrayList<Annotation>();
+ for (Annotation annotation : annotatedMember.getAnnotations())
+ {
+ if (beanManager.isQualifier(annotation.annotationType()))
+ {
+ qualifiers.add(annotation);
+ }
+ }
+
+ Typed typed = annotatedMember.getAnnotation(Typed.class);
+
+ List<Type> foundTypes = new ArrayList<Type>();
+ if (typed != null)
+ {
+ Collections.addAll(foundTypes, typed.value());
+ }
+ else if (annotatedMember.getBaseType() instanceof Class)
+ {
+ foundTypes.addAll(extractTypes((Class)annotatedMember.getBaseType()));
+ }
+
+ if (foundTypes.isEmpty())
+ {
+ return;
+ }
+
+ processProducer.setProducer(new MockAwareProducerWrapper<T>(
+ beanManager, originalProducer, foundTypes, qualifiers));
+ }
+
+ protected List<Type> extractTypes(Class currentClass)
+ {
+ List<Type> result = new ArrayList<Type>();
+ for (Class<?> c = currentClass; c != Object.class && c != null; c = c.getSuperclass())
+ {
+ result.add(c);
+ }
+ Collections.addAll(result, currentClass.getInterfaces());
+ return result;
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java
new file mode 100644
index 000000000..d3dd4439a
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleApplicationMockManager.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.mock.ApplicationMockManager;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+@Typed(ApplicationMockManager.class)
+public class SimpleApplicationMockManager extends AbstractMockManager implements ApplicationMockManager
+{
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java
new file mode 100644
index 000000000..f00699aff
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/mock/SimpleMockManager.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.mock;
+
+import jakarta.enterprise.inject.Typed;
+import org.apache.deltaspike.testcontrol5.api.mock.ApplicationMockManager;
+import org.apache.deltaspike.testcontrol5.api.mock.DynamicMockManager;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import java.lang.annotation.Annotation;
+
+@RequestScoped
+@Typed(DynamicMockManager.class)
+public class SimpleMockManager extends AbstractMockManager
+{
+ @Inject
+ private ApplicationMockManager applicationMockManager;
+
+ @Override
+ public <T> T getMock(Class<T> beanClass, Annotation... qualifiers)
+ {
+ T result = applicationMockManager.getMock(beanClass, qualifiers);
+
+ if (result != null)
+ {
+ return result;
+ }
+ return super.getMock(beanClass, qualifiers);
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java
new file mode 100644
index 000000000..32e6882a1
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/request/ContextControlDecorator.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.request;
+
+import org.apache.deltaspike.cdise.api.ContextControl;
+import org.apache.deltaspike.testcontrol5.api.junit.CdiTestExtension;
+import org.apache.deltaspike.testcontrol5.spi.ExternalContainer;
+
+import jakarta.decorator.Decorator;
+import jakarta.decorator.Delegate;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.ConversationScoped;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.lang.annotation.Annotation;
+
+@Decorator
+public class ContextControlDecorator implements ContextControl
+{
+ @Inject
+ @Delegate
+ private ContextControl wrapped;
+
+ @Override
+ public void startContexts()
+ {
+ wrapped.startContexts();
+
+ if (isManualScopeHandling())
+ {
+ for (ExternalContainer externalContainer : CdiTestExtension.getActiveExternalContainers())
+ {
+ externalContainer.startScope(Singleton.class);
+ externalContainer.startScope(ApplicationScoped.class);
+ externalContainer.startScope(RequestScoped.class);
+ externalContainer.startScope(SessionScoped.class);
+ externalContainer.startScope(ConversationScoped.class);
+ }
+ }
+ }
+
+ @Override
+ public void stopContexts()
+ {
+ if (isManualScopeHandling())
+ {
+ for (ExternalContainer externalContainer : CdiTestExtension.getActiveExternalContainers())
+ {
+ externalContainer.stopScope(ConversationScoped.class);
+ externalContainer.stopScope(SessionScoped.class);
+ externalContainer.stopScope(RequestScoped.class);
+ externalContainer.stopScope(ApplicationScoped.class);
+ externalContainer.stopScope(Singleton.class);
+ }
+ }
+
+ wrapped.stopContexts();
+ }
+
+ @Override
+ public void startContext(Class<? extends Annotation> scopeClass)
+ {
+ wrapped.startContext(scopeClass);
+
+ if (isManuallyHandledRequest(scopeClass))
+ {
+ for (ExternalContainer externalContainer : CdiTestExtension.getActiveExternalContainers())
+ {
+ externalContainer.startScope(scopeClass);
+ }
+ }
+ }
+
+ @Override
+ public void stopContext(Class<? extends Annotation> scopeClass)
+ {
+ wrapped.stopContext(scopeClass);
+
+ if (isManuallyHandledRequest(scopeClass))
+ {
+ for (ExternalContainer externalContainer : CdiTestExtension.getActiveExternalContainers())
+ {
+ externalContainer.stopScope(scopeClass);
+ }
+ }
+ }
+
+ private boolean isManuallyHandledRequest(Class<? extends Annotation> scopeClass)
+ {
+ return RequestScoped.class.equals(scopeClass) && isManualScopeHandling();
+ }
+
+ private boolean isManualScopeHandling()
+ {
+ return !Boolean.TRUE.equals(CdiTestExtension.isAutomaticScopeHandlingActive());
+ }
+}
diff --git a/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java
new file mode 100644
index 000000000..35461e9a6
--- /dev/null
+++ b/deltaspike/modules/test-control5/impl/src/main/java/org/apache/deltaspike/testcontrol5/impl/validation/StandardContextTestControlValidator.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.deltaspike.testcontrol5.impl.validation;
+
+import org.apache.deltaspike.cdise.api.CdiContainerLoader;
+import org.apache.deltaspike.testcontrol5.api.TestControl;
+import org.apache.deltaspike.testcontrol5.spi.TestAware;
+import org.apache.deltaspike.testcontrol5.spi.TestControlValidator;
+
+import jakarta.enterprise.inject.Vetoed;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Vetoed
+public class StandardContextTestControlValidator implements TestAware, TestControlValidator
+{
+ private static Boolean customContextControlDetected;
+
+ private static ThreadLocal<Class> currentTestClass = new ThreadLocal<Class>();
+ private static ThreadLocal<Method> currentTestMethod = new ThreadLocal<Method>();
+
+ @Override
+ public void validate(TestControl testControl)
+ {
+ checkActiveContextControlImplementation();
+
+ List<Class<? extends Annotation>> scopeClasses = new ArrayList<Class<? extends Annotation>>();
+ Collections.addAll(scopeClasses, testControl.startScopes());
+
+ validateSupportedScopes(scopeClasses, currentTestClass.get(), currentTestMethod.get());
+ }
+
+ private void checkActiveContextControlImplementation()
+ {
+ if (customContextControlDetected != null)
+ {
+ return;
+ }
+
+ customContextControlDetected = !CdiContainerLoader.getCdiContainer().getContextControl()
+ .getClass().getName().startsWith("org.apache.deltaspike.");
+ }
+
+ private void validateSupportedScopes(List<Class<? extends Annotation>> scopeClasses,
+ Class<?> declaringClass,
+ Method testMethod)
+ {
+ if (Boolean.TRUE.equals(customContextControlDetected))
+ {
+ return;
+ }
+
+ for (Class<? extends Annotation> scopeClass : scopeClasses)
+ {
+ if (!scopeClass.getName().startsWith("jakarta.enterprise.context."))
+ {
+ throw new IllegalStateException("Please remove " + scopeClass.getName() + " at " + declaringClass +
+ (testMethod != null ? "#" + testMethod.getName() : "") +
+ " from @" + TestControl.class.getName() + ". @" + TestControl.class.getName() +
+ " only supports standard Scope-Annotations provided by the CDI-Specification. " +
+ "Other Contexts start automatically or need to get started with a specific Management-API. " +
+ "Examples: " +
+ "@TransactionScoped gets started automatically once the @Transactional-Interceptor is used. " +
+ "Whereas @WindowScoped starts once WindowContext#activateWindow gets called.");
+ }
+ }
+ }
+
+ @Override
+ public void setTestClass(Class testClass)
+ {
+ currentTestClass.set(testClass);
+ if (testClass == null)
+ {
+ currentTestClass.remove();
+ }
+ }
+
+ @Override
+ public void setTestMethod(Method testMethod)
+ {
+ currentTestMethod.set(testMethod);
+ if (testMethod == null)
+ {
+ currentTestMethod.remove();
+ }
+ }
+}
diff --git a/deltaspike/modules/pom.xml b/deltaspike/modules/test-control5/pom.xml
similarity index 70%
copy from deltaspike/modules/pom.xml
copy to deltaspike/modules/test-control5/pom.xml
index d4017033f..7d230af65 100644
--- a/deltaspike/modules/pom.xml
+++ b/deltaspike/modules/test-control5/pom.xml
@@ -21,27 +21,20 @@
<modelVersion>4.0.0</modelVersion>
<parent>
- <groupId>org.apache.deltaspike</groupId>
- <artifactId>parent-code</artifactId>
+ <groupId>org.apache.deltaspike.modules</groupId>
+ <artifactId>modules-project</artifactId>
<version>2.0.2-SNAPSHOT</version>
- <relativePath>../parent/code/pom.xml</relativePath>
+ <relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.apache.deltaspike.modules</groupId>
- <artifactId>modules-project</artifactId>
- <version>2.0.2-SNAPSHOT</version>
+ <artifactId>test-control5-module-project</artifactId>
<packaging>pom</packaging>
- <name>Apache DeltaSpike Modules</name>
+ <name>Apache DeltaSpike Test-Control5-Module (JUnit 5)</name>
<modules>
- <module>proxy</module>
- <module>security</module>
- <module>jsf</module>
- <module>partial-bean</module>
- <module>jpa</module>
- <module>data</module>
- <module>scheduler</module>
- <module>test-control</module>
+ <module>api</module>
+ <module>impl</module>
</modules>
</project>