Skip to content

Commit

Permalink
Convert Condition to a first-class TestExtension
Browse files Browse the repository at this point in the history
- Condition now extends TestExtension.
- @conditional has been removed.
- Conditions are now registered via @ExtendWith
- DisabledCondition is now a top-level class within the JUnit 5 Engine.
- DisabledCondition is now registered by default.
- ConditionEvaluator now retrieves Conditions from the execution context.
- Introduced example @SystemProperty support in DisabledTests.
  • Loading branch information
sbrannen committed Nov 25, 2015
1 parent 5531679 commit 7caa983
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 152 deletions.
49 changes: 0 additions & 49 deletions junit5-api/src/main/java/org/junit/gen5/api/Disabled.java
Expand Up @@ -10,20 +10,11 @@

package org.junit.gen5.api;

import static org.junit.gen5.commons.util.AnnotationUtils.findAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import java.util.function.Function;

import org.junit.gen5.api.extension.Condition;
import org.junit.gen5.api.extension.Conditional;
import org.junit.gen5.api.extension.TestExecutionContext;
import org.junit.gen5.commons.util.StringUtils;

/**
* {@code @Disabled} is used to signal that the annotated test class or
Expand All @@ -38,51 +29,11 @@
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(Disabled.DisabledCondition.class)
public @interface Disabled {

/**
* The reason this test is disabled.
*/
String value() default "";

static class DisabledCondition implements Condition {

/**
* Tests are disabled if {@code @Disabled} is either present on the
* test class or on the test method.
*/
@Override
public Result evaluate(TestExecutionContext context) {

// Class level?
Optional<Disabled> disabled = findAnnotation(context.getTestClass(), Disabled.class);
Function<TestExecutionContext, String> reasonBuilder = DisabledCondition::buildClassLevelReason;

// Method level?
if (!disabled.isPresent()) {
disabled = findAnnotation(context.getTestMethod(), Disabled.class);
reasonBuilder = DisabledCondition::buildMethodLevelReason;
}

if (disabled.isPresent()) {
String reason = disabled.map(Disabled::value).filter(StringUtils::isNotBlank).orElse(
reasonBuilder.apply(context));
return Result.disabled(reason);
}

return Result.enabled("@Disabled is not present");
}

private static String buildClassLevelReason(TestExecutionContext context) {
return String.format("@Disabled is present on test class [%s]", context.getTestClass().get().getName());
}

private static String buildMethodLevelReason(TestExecutionContext context) {
return String.format("@Disabled is present on test method [%s]",
context.getTestMethod().get().toGenericString());
}

}

}
Expand Up @@ -16,26 +16,22 @@
import lombok.ToString;

/**
* {@code Condition} defines the test extension API for programmatic,
* {@code Condition} defines the {@link TestExtension} API for programmatic,
* <em>conditional test execution</em>.
*
* <p>A {@code Condition} is {@linkplain #evaluate evaluated} to determine
* if a given test (e.g., class or method) should be executed based on the
* supplied {@link TestExecutionContext}.
* supplied {@link TestExecutionContext}. When evaluated at the class level,
* a condition applies to all test methods within that class.
*
* <p>Implementations must be registered via {@link Conditional @Conditional}
* and must provide a no-args constructor.
*
* <p>When registered at the class level, a {@code Condition} applies to
* all test methods within that class.
* <p>Implementations must provide a no-args constructor.
*
* @author Sam Brannen
* @since 5.0
* @see Conditional
* @see org.junit.gen5.api.Disabled
*/
@FunctionalInterface
public interface Condition {
public interface Condition extends TestExtension {

/**
* Evaluate this condition for the supplied {@link TestExecutionContext}.
Expand Down

This file was deleted.

Expand Up @@ -25,8 +25,13 @@
*
* <h3>Supported Extension APIs</h3>
* <ul>
* <li>{@link Condition}</li>
* <li>{@link InstancePostProcessor}</li>
* <li>{@link MethodParameterResolver}</li>
* <li>{@link BeforeEachCallbacks}</li>
* <li>{@link AfterEachCallbacks}</li>
* <li>{@link BeforeAllCallbacks}</li>
* <li>{@link AfterAllCallbacks}</li>
* </ul>
*
* @author Sam Brannen
Expand Down
Expand Up @@ -10,70 +10,48 @@

package org.junit.gen5.engine.junit5.execution;

import static org.junit.gen5.commons.util.AnnotationUtils.findAnnotation;

import java.util.Arrays;
import java.util.Optional;

import org.junit.gen5.api.extension.Condition;
import org.junit.gen5.api.extension.Condition.Result;
import org.junit.gen5.api.extension.Conditional;
import org.junit.gen5.api.extension.TestExecutionContext;
import org.junit.gen5.commons.util.ReflectionUtils;

/**
* {@code ConditionEvaluator} evaluates {@link Condition Conditions}
* configured via {@link Conditional @Conditional}.
* {@code ConditionEvaluator} evaluates all {@link Condition Conditions}
* registered in a {@link TestExecutionContext}.
*
* @author Sam Brannen
* @since 5.0
* @see Conditional
* @see Condition
*/
class ConditionEvaluator {

private static final Result ENABLED = Result.enabled("No 'disabled' conditions encountered");

/**
* Evaluate all {@link Condition Conditions} configured via
* {@link Conditional @Conditional} for the supplied {@link TestExecutionContext}.
* Evaluate all {@link Condition Conditions} registered for the supplied
* {@link TestExecutionContext}.
*
* @param context the current {@code TestExecutionContext}
* @return the first <em>disabled</em> {@code Result}, or an <em>enabled</em>
* {@code Result} if no disabled conditions are encountered.
* @return the first <em>disabled</em> {@code Result}, or a default
* <em>enabled</em> {@code Result} if no disabled conditions are
* encountered.
*/
Result evaluate(TestExecutionContext context) {
Result result = ENABLED;

// TODO Introduce support for finding *all* @Conditional annotations.
Optional<Conditional> classLevelAnno = findAnnotation(context.getTestClass(), Conditional.class);

// TODO Don't search at the method level if disabled at the class level.
Optional<Conditional> methodLevelAnno = findAnnotation(context.getTestMethod(), Conditional.class);

Conditional conditional = classLevelAnno.orElse(methodLevelAnno.orElse(null));

if (conditional != null) {
// @formatter:off
result = Arrays.stream(conditional.value())
.map(clazz -> evaluate(context, clazz))
.filter(Result::isDisabled)
.findFirst()
.orElse(ENABLED);
// @formatter:on
}

return result;
// @formatter:off
return context.getExtensions(Condition.class)
.map(condition -> evaluate(context, condition))
.filter(Result::isDisabled)
.findFirst()
.orElse(ENABLED);
// @formatter:on
}

private Result evaluate(TestExecutionContext context, Class<? extends Condition> conditionClass) {
private Result evaluate(TestExecutionContext context, Condition condition) {
try {
Condition condition = ReflectionUtils.newInstance(conditionClass);
return condition.evaluate(context);
}
catch (Exception e) {
catch (Exception ex) {
throw new IllegalStateException(
String.format("Failed to evaluate condition [%s]", conditionClass.getName()), e);
String.format("Failed to evaluate condition [%s]", condition.getClass().getName()), ex);
}
}

Expand Down
Expand Up @@ -19,6 +19,7 @@

import org.junit.gen5.api.extension.TestExtension;
import org.junit.gen5.commons.util.ReflectionUtils;
import org.junit.gen5.engine.junit5.extension.DisabledCondition;
import org.junit.gen5.engine.junit5.extension.TestNameParameterResolver;

/**
Expand All @@ -28,7 +29,7 @@
public class TestExtensionRegistry {

private static final List<Class<? extends TestExtension>> defaultExtensionClasses = Collections.unmodifiableList(
Arrays.asList(TestNameParameterResolver.class));
Arrays.asList(DisabledCondition.class, TestNameParameterResolver.class));

static List<Class<? extends TestExtension>> getDefaultExtensionClasses() {
return defaultExtensionClasses;
Expand Down
@@ -0,0 +1,54 @@
/*
* Copyright 2015 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.gen5.engine.junit5.extension;

import static org.junit.gen5.commons.util.AnnotationUtils.findAnnotation;

import java.util.Optional;

import org.junit.gen5.api.Disabled;
import org.junit.gen5.api.extension.Condition;
import org.junit.gen5.api.extension.TestExecutionContext;
import org.junit.gen5.commons.util.StringUtils;

/**
* {@link Condition} that supports the {@link Disabled @Disabled} annotation.
*
* @author Sam Brannen
* @since 5.0
* @see Disabled
*/
public class DisabledCondition implements Condition {

/**
* Tests are disabled if {@code @Disabled} is either present on the
* test class or on the test method.
*/
@Override
public Result evaluate(TestExecutionContext context) {

// Class level?
Optional<Disabled> disabled = findAnnotation(context.getTestClass(), Disabled.class);

// Method level?
if (!disabled.isPresent()) {
disabled = findAnnotation(context.getTestMethod(), Disabled.class);
}

if (disabled.isPresent()) {
String reason = disabled.map(Disabled::value).filter(StringUtils::isNotBlank).orElse("Test is @Disabled");
return Result.disabled(reason);
}

return Result.enabled("@Disabled is not present");
}

}
Expand Up @@ -19,7 +19,7 @@
import org.junit.gen5.api.extension.TestExecutionContext;

/**
* {@code MethodParameterResolver} that resolves the name of the currently
* {@link MethodParameterResolver} that resolves the name of the currently
* executing test for {@code String} method parameters annotated with
* {@link TestName @TestName}.
*
Expand Down

0 comments on commit 7caa983

Please sign in to comment.