/*
* Copyright 2015-2017 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.junit.jupiter.engine.execution;
import static java.lang.String.format;
import static org.junit.jupiter.engine.Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN;
import static org.junit.jupiter.engine.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME;
import static org.junit.platform.commons.meta.API.Usage.Internal;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ContainerExecutionCondition;
import org.junit.jupiter.api.extension.ContainerExtensionContext;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionCondition;
import org.junit.jupiter.api.extension.TestExtensionContext;
import org.junit.jupiter.engine.Constants;
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.platform.commons.meta.API;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.ConfigurationParameters;
/**
* {@code ConditionEvaluator} evaluates {@link ContainerExecutionCondition}
* and {@link TestExecutionCondition} extensions.
*
* @since 5.0
* @see ContainerExecutionCondition
* @see TestExecutionCondition
*/
@API(Internal)
public class ConditionEvaluator {
private static final Logger LOG = Logger.getLogger(ConditionEvaluator.class.getName());
private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled(
"No 'disabled' conditions encountered");
private static final Predicate<Object> alwaysActivated = condition -> true;
private static final Predicate<Object> alwaysDeactivated = condition -> false;
/**
* Evaluate all {@link ContainerExecutionCondition}
* extensions registered for the supplied {@link ContainerExtensionContext}.
*
* @param context the current {@code ContainerExtensionContext}
* @return the first <em>disabled</em> {@code ConditionEvaluationResult},
* or a default <em>enabled</em> {@code ConditionEvaluationResult} if no
* disabled conditions are encountered
*/
public ConditionEvaluationResult evaluateForContainer(ExtensionRegistry extensionRegistry,
ConfigurationParameters configurationParameters, ContainerExtensionContext context) {
BiFunction<Object, Object, ConditionEvaluationResult> evaluateAdaptor = (condition,
ctx) -> evaluate((ContainerExecutionCondition) condition, (ContainerExtensionContext) ctx);
return evaluate(ContainerExecutionCondition.class, evaluateAdaptor, extensionRegistry, configurationParameters,
context);
}
/**
* Evaluate all {@link TestExecutionCondition}
* extensions registered for the supplied {@link TestExtensionContext}.
*
* @param context the current {@code TestExtensionContext}
* @return the first <em>disabled</em> {@code ConditionEvaluationResult},
* or a default <em>enabled</em> {@code ConditionEvaluationResult} if no
* disabled conditions are encountered
*/
public ConditionEvaluationResult evaluateForTest(ExtensionRegistry extensionRegistry,
ConfigurationParameters configurationParameters, TestExtensionContext context) {
BiFunction<Object, Object, ConditionEvaluationResult> evaluateAdaptor = (condition,
ctx) -> evaluate((TestExecutionCondition) condition, (TestExtensionContext) ctx);
return evaluate(TestExecutionCondition.class, evaluateAdaptor, extensionRegistry, configurationParameters,
context);
}
private ConditionEvaluationResult evaluate(Class<? extends Extension> extensionType,
BiFunction<Object, Object, ConditionEvaluationResult> evaluateAdaptor, ExtensionRegistry extensionRegistry,
ConfigurationParameters configurationParameters, ExtensionContext context) {
Predicate<Object> isActivated = conditionIsActivated(configurationParameters);
// @formatter:off
return extensionRegistry.stream(extensionType)
.filter(isActivated)
.map(condition -> evaluateAdaptor.apply(condition, context))
.filter(ConditionEvaluationResult::isDisabled)
.findFirst()
.orElse(ENABLED);
// @formatter:on
}
private ConditionEvaluationResult evaluate(ContainerExecutionCondition condition,
ContainerExtensionContext context) {
try {
ConditionEvaluationResult result = condition.evaluateContainerExecutionCondition(context);
logResult(condition.getClass(), result);
return result;
}
catch (Exception ex) {
throw evaluationException(condition.getClass(), ex);
}
}
private ConditionEvaluationResult evaluate(TestExecutionCondition condition, TestExtensionContext context) {
try {
ConditionEvaluationResult result = condition.evaluateTestExecutionCondition(context);
logResult(condition.getClass(), result);
return result;
}
catch (Exception ex) {
throw evaluationException(condition.getClass(), ex);
}
}
private void logResult(Class<?> conditionType, ConditionEvaluationResult result) {
LOG.finer(() -> format("Evaluation of condition [%s] resulted in: %s", conditionType.getName(), result));
}
private ConditionEvaluationException evaluationException(Class<?> conditionType, Exception ex) {
String cause = StringUtils.isNotBlank(ex.getMessage()) ? ": " + ex.getMessage() : "";
return new ConditionEvaluationException(
format("Failed to evaluate condition [%s]%s", conditionType.getName(), cause), ex);
}
private Predicate<Object> conditionIsActivated(ConfigurationParameters configurationParameters) {
String patternString = getDeactivatePatternString(configurationParameters);
if (patternString != null) {
if (DEACTIVATE_ALL_CONDITIONS_PATTERN.equals(patternString)) {
return alwaysDeactivated;
}
Pattern pattern = Pattern.compile(convertToRegEx(patternString));
return condition -> !pattern.matcher(condition.getClass().getName()).matches();
}
return alwaysActivated;
}
private String getDeactivatePatternString(ConfigurationParameters configurationParameters) {
// @formatter:off
return configurationParameters.get(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME)
.filter(StringUtils::isNotBlank)
.map(String::trim)
.orElse(null);
// @formatter:on
}
/**
* See {@link Constants#DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME} for
* details on the pattern matching syntax.
*/
private String convertToRegEx(String pattern) {
pattern = Matcher.quoteReplacement(pattern);
// Match "." against "." and "$" since users may declare a "." instead of a
// "$" as the separator between classes and nested classes.
pattern = pattern.replace(".", "[.$]");
// Convert our "*" wildcard into a proper RegEx pattern.
pattern = pattern.replace("*", ".+");
return pattern;
}
}