All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.gov.gchq.gaffer.integration.junit.extensions.IntegrationTestSuiteExtension Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022 Crown Copyright
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.gov.gchq.gaffer.integration.junit.extensions;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.gov.gchq.gaffer.store.StoreProperties;
import uk.gov.gchq.gaffer.store.schema.Schema;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;

import static org.junit.platform.commons.support.ReflectionSupport.tryToLoadClass;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields;
import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible;
import static org.junit.platform.commons.util.ReflectionUtils.newInstance;

/**
 * 

* The {@link IntegrationTestSuiteExtension} retrieves the {@link Schema}, the * {@link StoreProperties} and the {@code tests-to-skip} {@link Map} from the * {@link IntegrationTestSuite} class. The values are then used by the test * classes from the {@link org.junit.platform.suite.api.Suite} on execution or * to exclude the tests listed in the tests-to-skip {@link Map}. *

*

* The required {@link Schema} and {@link StoreProperties} are injected in to * the test instance before each test is run. This injection is either at field * level (see {@link #beforeEach(ExtensionContext)}) or as * {@link java.lang.reflect.Method} parameters (see * {@link #resolveParameter(ParameterContext, ExtensionContext)}). *

*

* For the {@code tests-to-skip} {@link Map}, each test method is checked before * execution and if the method {@link Map#containsKey(Object)} then the test is * omitted. *

*

* In order to find the {@link Class} containing the {@link Schema}, the * {@link StoreProperties} and {@code tests-to-skip} {@link Map}, the * {@link IntegrationTestSuiteExtension} must first instantiate the * {@link Class}. This is done through advertising the {@link Class} * name using a {@link org.junit.platform.suite.api.ConfigurationParameter}. The * {@link Class} must also extend {@link IntegrationTestSuite} so the data can * be retrieved by the {@link IntegrationTestSuiteExtension}. For example: *

*
 * 
 *
 * package integration.tests;
 *
 * import static uk.gov.gchq.gaffer.integration.junit.extensions.IntegrationTestSuiteExtension.INIT_CLASS;
 *
 * @Suite
 * @SelectPackages("root.test.packages.to.search")
 * @IncludeClassNamePatterns(".*IT")
 * @ConfigurationParameter(key = INIT_CLASS, value = "integration.tests.IntegrationTestSuiteITs")
 * public class IntegrationTestSuiteITs extends IntegrationTestSuite {
 *     public IntegrationTestSuiteITs() {
 *         setSchema(...);
 *         setStoreProperties(...);
 *         setTestsToSkip(...);
 *     }
 * }
 * 
 * 
*

* In this example, the {@link IntegrationTestSuite} {@link Class} advertised is * the same as the {@link org.junit.platform.suite.api.Suite} {@link Class}. * However, you can advertise any {@link Class} as long as it implements * {@link IntegrationTestSuite}. *

*/ public class IntegrationTestSuiteExtension implements ParameterResolver, BeforeAllCallback, BeforeEachCallback, ExecutionCondition { static { ExtensionContext.Namespace.create(IntegrationTestSuiteExtension.class); } public static final String INIT_CLASS = "initClass"; private static final Logger LOGGER = LoggerFactory.getLogger(IntegrationTestSuiteExtension.class); private static final Map INTEGRATION_TEST_SUITE_CLASS_MAP = new HashMap<>(); private Schema schema; private StoreProperties storeProperties; private Map skipTestMethods; /** *

* The {@code beforeAll} {@link java.lang.reflect.Method} is used to load * the {@link Class} implementing {@link IntegrationTestSuite}. This is * required for injecting the {@link Schema} and {@link StoreProperties} and * checking whether the tests are enabled using the {@code tests-to-skip} * {@link Map}. *

*

* The {@code beforeAll} method first checks if the {@code INIT_CLASS} * has been set and if so attempts to retrieve the * {@link IntegrationTestSuite} {@link Object} from the cache. If the cache * does not contain the instance then it attempts instantiation. If there * are errors during the instantiation then {@link Exception}s * are thrown and the {@link org.junit.platform.suite.api.Suite} fails. *

* * @param extensionContext the current extension context; never {@code null} */ @Override public void beforeAll(final ExtensionContext extensionContext) { final Optional initClassOptional = extensionContext.getConfigurationParameter(INIT_CLASS); if (initClassOptional.isPresent()) { LOGGER.debug("Initialisation class [{}] found", initClassOptional.get()); final IntegrationTestSuite integrationTestSuite = getIntegrationTestSuite(initClassOptional.get()); this.schema = integrationTestSuite.getSchema() .orElseThrow(NoSuchElementException::new); this.storeProperties = integrationTestSuite.getStoreProperties() .orElseThrow(NoSuchElementException::new); this.skipTestMethods = integrationTestSuite.getTestsToSkip() .orElse(Collections.emptyMap()); } else { throw new IllegalArgumentException("The initClass @ConfigurationParameter has not been set"); } } /** * The {@code beforeEach} {@link java.lang.reflect.Method} is called before * each test method is run. In the case of the * {@link IntegrationTestSuiteExtension}, if any of the fields are annotated * with the {@code @InjectedFromStoreITsSuite} annotation, the value is * checked to see if it is {@link Schema} or a {@link StoreProperties} and * if so the value is made accessible before the test is run. If * the value is not found then a {@link ParameterResolutionException} is * thrown. * For example: *
     * 
     * class TestIT {
     *     @InjectedFromStoreITsSuite
     *     Schema schema;
     *     @InjectedFromStoreITsSuite
     *     StoreProperties storeProperties;
     *     @Test
     *     void test() {
     *         ....
     *     }
     * }
     * 
     * 
* * @param context the current extension context; never {@code null} */ @Override public void beforeEach(final ExtensionContext context) { context.getRequiredTestInstances() .getAllInstances() .forEach(this::injectInstanceFields); } /** * The {@code supportsParameter} {@link java.lang.reflect.Method} returns * {@code true} if the parameter is either a {@link Schema} or a * {@link StoreProperties} or {@code false} otherwise. * * @param parameterContext the context for the parameter for which an * argument should be resolved; never {@code null} * @param extensionContext the extension context for the {@code Executable} * about to be invoked; never {@code null} * @return true if the parameter is found, false otherwise */ @Override public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) { final Class type = parameterContext.getParameter().getType(); return type.isAssignableFrom(Schema.class) || type.isAssignableFrom(StoreProperties.class); } /** * The {@code resolveParameter} {@link java.lang.reflect.Method} is called * everytime a parameter passes the * {@link #supportsParameter(ParameterContext, ExtensionContext)} check. The * {@link ParameterResolver} type is checked and if it is a {@link Schema} * or a {@link StoreProperties} the corresponding value is returned. A * {@link ParameterResolutionException} is thrown if there are no matches. * The parameters should be annotated with * {@code @InjectedFromStoreITsSuite} * {@link java.lang.annotation.Annotation}. For example: *
     * 
     *
     * void test(@InjectedFromStoreITsSuite final Schema schema) {
     *     ....
     * }
     * 
     * 
* * @param parameterContext the context for the parameter for which an * argument should be resolved; never {@code null} * @param extensionContext the extension context for the {@code Executable} * about to be invoked; never {@code null} * @return the {@link Object} matching the {@link ParameterResolver} type * @throws ParameterResolutionException if the {@link Object} matching the * {@link ParameterResolver} type * cannot be found */ @Override public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException { final Class type = parameterContext.getParameter().getType(); return getObject(type); } /** * The {@code evaluateExecutionCondition} {@link java.lang.reflect.Method} * allows the disabling of tests if some condition is met. In this case the * test is skipped if the test method is in the {@code tests-to-skip} * {@link Map} provided the {@link Class} implementing * {@link IntegrationTestSuite}. * * @param context the current extension context; never {@code null} * @return a {@link ConditionEvaluationResult#enabled(String)} or * {@link ConditionEvaluationResult#disabled(String)} {@link Object} for the * {@link org.junit.jupiter.api.Test} in question */ @Override public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) { if (context.getTestMethod().isPresent()) { final String currentMethodName = context.getTestMethod().get().getName(); if (this.skipTestMethods.containsKey(currentMethodName)) { return ConditionEvaluationResult.disabled(this.skipTestMethods.get(currentMethodName)); } } return ConditionEvaluationResult.enabled("Test enabled"); } private void injectInstanceFields(final Object instance) { final List annotatedFields = findAnnotatedFields(instance.getClass(), InjectedFromStoreITsSuite.class, ReflectionUtils::isNotStatic); for (final Field annotatedField : annotatedFields) { try { LOGGER.debug("Field [{}] requires injecting", annotatedField); final Object object = getObject(annotatedField.getType()); LOGGER.debug("Object [{}] found for the field", object); makeAccessible(annotatedField).set(instance, object); } catch (final Throwable t) { throw new RuntimeException("Error accessing the field object", t); } } } private Object getObject(final Class type) { if (type.isAssignableFrom(Schema.class)) { return this.schema; } else if (type.isAssignableFrom(StoreProperties.class)) { return this.storeProperties; } else { throw new ParameterResolutionException(String.format("Object of type [%s] not found", type)); } } private static IntegrationTestSuite getIntegrationTestSuite(final String initClass) { final IntegrationTestSuite integrationTestSuite; if (INTEGRATION_TEST_SUITE_CLASS_MAP.containsKey(initClass)) { integrationTestSuite = INTEGRATION_TEST_SUITE_CLASS_MAP.get(initClass); } else { final Optional> classOptional = tryToLoadClass(initClass).toOptional(); if (classOptional.isPresent()) { final Object object = newInstance(classOptional.get()); if (object instanceof IntegrationTestSuite) { integrationTestSuite = (IntegrationTestSuite) object; } else { throw new ParameterResolutionException(String.format("The object was not of required type: [%s]. Actual object type: [%s]", IntegrationTestSuite.class.getName(), object.getClass().getName())); } INTEGRATION_TEST_SUITE_CLASS_MAP.put(initClass, integrationTestSuite); } else { throw new ParameterResolutionException(String.format("A class could not be loaded for initClass [%s]", initClass)); } } return integrationTestSuite; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy