/*
 * Copyright 2015-2019 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 v2.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.engine.extension;

import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;

import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.engine.script.Script;
import org.junit.platform.commons.util.BlacklistedExceptions;

ExecutionCondition that supports the DisabledIf and EnabledIf annotation.
See Also:
Since:5.1
/** * {@link ExecutionCondition} that supports the {@link DisabledIf} and {@link EnabledIf} annotation. * * @since 5.1 * @see DisabledIf * @see EnabledIf * @see #evaluateExecutionCondition(ExtensionContext) */
class ScriptExecutionCondition implements ExecutionCondition { private static final ConditionEvaluationResult ENABLED_NO_ELEMENT = enabled("AnnotatedElement not present"); private static final ConditionEvaluationResult ENABLED_NO_ANNOTATION = enabled("Annotation not present"); private static final String EVALUATOR_CLASS_NAME = "org.junit.jupiter.engine.extension.ScriptExecutionEvaluator"; private final Evaluator evaluator; // Used by the ExtensionRegistry. ScriptExecutionCondition() { this(EVALUATOR_CLASS_NAME); } // Used by tests. ScriptExecutionCondition(String evaluatorImplementationName) { this.evaluator = Evaluator.forName(evaluatorImplementationName); } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { // Context without an annotated element? Optional<AnnotatedElement> element = context.getElement(); if (!element.isPresent()) { return ENABLED_NO_ELEMENT; } AnnotatedElement annotatedElement = element.get(); // Always try to create script instances. Script disabledScript = createDisabledIfScriptOrNull(annotatedElement); Script enabledScript = createEnabledIfScriptOrNull(annotatedElement); // If no scripts are created, no annotation of interest is attached to the underlying element. if (disabledScript == null && enabledScript == null) { return ENABLED_NO_ANNOTATION; } // Prepare list with single or two script elements. List<Script> scripts = new ArrayList<>(); if (disabledScript != null) { scripts.add(disabledScript); } if (enabledScript != null) { scripts.add(enabledScript); } // Let the evaluator do its work. return evaluator.evaluate(context, scripts); } private Script createDisabledIfScriptOrNull(AnnotatedElement annotatedElement) { Optional<DisabledIf> disabled = findAnnotation(annotatedElement, DisabledIf.class); if (!disabled.isPresent()) { return null; } DisabledIf annotation = disabled.get(); String source = createSource(annotation.value()); return new Script(annotation, annotation.engine(), source, annotation.reason()); } private Script createEnabledIfScriptOrNull(AnnotatedElement annotatedElement) { Optional<EnabledIf> enabled = findAnnotation(annotatedElement, EnabledIf.class); if (!enabled.isPresent()) { return null; } EnabledIf annotation = enabled.get(); String source = createSource(annotation.value()); return new Script(annotation, annotation.engine(), source, annotation.reason()); } private String createSource(String[] lines) { return String.join(System.lineSeparator(), lines); }
Evaluates scripts and returns a conditional evaluation result.
/** * Evaluates scripts and returns a conditional evaluation result. */
interface Evaluator { ConditionEvaluationResult evaluate(ExtensionContext context, List<Script> scripts);
Create evaluator via reflection to hide the `javax.script` dependency.

This method may return a ThrowingEvaluator instance on JREs that don't provide the "javax.script" package at all. It also returns such an instance on JREs that are launched with an active module system using insufficient module graphs, i.e. the application does not read java.scripting module.

See Also:
/** * Create evaluator via reflection to hide the `javax.script` dependency. * * <p>This method may return a {@link ThrowingEvaluator} instance on JREs that * don't provide the "javax.script" package at all. It also returns such an * instance on JREs that are launched with an active module system using * insufficient module graphs, i.e. the application does not read * {@code java.scripting} module. * * @see Class#forName(String) */
static Evaluator forName(String name) { // Assert that "javax.script.ScriptEngine" is loadable via basic reflection. return forName("javax.script.ScriptEngine", name); } static Evaluator forName(String nameOfScriptEngine, String name) { // Assert that precondition name is loadable via basic reflection. try { Class.forName(nameOfScriptEngine); } catch (Throwable cause) { BlacklistedExceptions.rethrowIfBlacklisted(cause); String message = "Class `" + nameOfScriptEngine + "` is not loadable, " // + "script-based test execution is disabled. " // + "If the originating cause is a `NoClassDefFoundError: javax/script/...` and " // + "the underlying runtime environment is executed with an activated module system " // + "(aka Jigsaw or JPMS) you need to add the `java.scripting` module to the " // + "root modules via `--add-modules ...,java.scripting`"; return new ThrowingEvaluator(message, cause); } // Now create the evaluator instance specified by its class name. try { return (Evaluator) Class.forName(name).getDeclaredConstructor().newInstance(); } catch (ReflectiveOperationException cause) { String message = "Creating instance of class `" + name + "` failed," // + "script-based test execution is disabled."; return new ThrowingEvaluator(message, cause); } } }
Evaluator implementation that always throws an ExtensionConfigurationException.
/** * Evaluator implementation that always throws an {@link ExtensionConfigurationException}. */
static class ThrowingEvaluator implements Evaluator { final ExtensionConfigurationException exception; ThrowingEvaluator(String message, Throwable cause) { this.exception = new ExtensionConfigurationException(message, cause); } @Override public ConditionEvaluationResult evaluate(ExtensionContext context, List<Script> scripts) { throw exception; } } }