Browse Source
* ttddyy-SPR-14614: Revise @DisabledIf support for JUnit Jupiter Introduce @DisabledIf annotation for JUnit 5pull/1149/head
5 changed files with 426 additions and 2 deletions
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* Copyright 2002-2016 the original author or authors. |
||||
* |
||||
* 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 org.springframework.test.context.junit.jupiter; |
||||
|
||||
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 org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.core.annotation.AliasFor; |
||||
|
||||
/** |
||||
* {@code @DisabledIf} is used to signal that the annotated test class or test |
||||
* method is <em>disabled</em> and should not be executed if the supplied |
||||
* {@link #expression} evaluates to {@code true}. |
||||
* |
||||
* <p>When applied at the class level, all test methods within that class
|
||||
* are automatically disabled as well. |
||||
* |
||||
* <p>For basic examples, see the Javadoc for {@link #expression}. |
||||
* |
||||
* <p>This annotation may be used as a <em>meta-annotation</em> to create |
||||
* custom <em>composed annotations</em>. For example, a custom |
||||
* {@code @DisabledOnMac} annotation can be created as follows. |
||||
* |
||||
* <pre style="code"> |
||||
* {@literal @}Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
* {@literal @}Retention(RetentionPolicy.RUNTIME) |
||||
* {@literal @}DisabledIf( |
||||
* expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", |
||||
* reason = "Disabled on Mac OS" |
||||
* ) |
||||
* public {@literal @}interface DisabledOnMac {} |
||||
* </pre> |
||||
* |
||||
* @author Sam Brannen |
||||
* @author Tadaya Tsuyukubo |
||||
* @since 5.0 |
||||
* @see SpringExtension |
||||
* @see org.junit.jupiter.api.Disabled |
||||
*/ |
||||
@Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
@ExtendWith(DisabledIfCondition.class) |
||||
public @interface DisabledIf { |
||||
|
||||
/** |
||||
* Alias for {@link #expression}; only intended to be used if an |
||||
* explicit {@link #reason} is not provided. |
||||
* |
||||
* @see #expression |
||||
*/ |
||||
@AliasFor("expression") |
||||
String value() default ""; |
||||
|
||||
/** |
||||
* The expression that will be evaluated to determine if the annotated test |
||||
* class or test method is <em>disabled</em>. |
||||
* |
||||
* <p>If the expression evaluates to {@link Boolean#TRUE} or a {@link String} |
||||
* equal to {@code "true"} (ignoring case), the test will be disabled. |
||||
* |
||||
* <p>Expressions can be any of the following. |
||||
* |
||||
* <ul> |
||||
* <li>Spring Expression Language (SpEL) expression — for example: |
||||
* <pre style="code">@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")</pre> |
||||
* <li>Placeholder for a property available in the Spring |
||||
* {@link org.springframework.core.env.Environment Environment} — for example: |
||||
* <pre style="code">@DisabledIf("${smoke.tests.enabled}")</pre> |
||||
* <li>Text literal — for example: |
||||
* <pre style="code">@DisabledIf("true")</pre> |
||||
* </ul> |
||||
* |
||||
* <p>Note, however, that a <em>text literal</em> which is not the result of |
||||
* dynamic resolution of a property placeholder is of zero practical value |
||||
* since {@code @DisabledIf("true")} is equivalent to {@code @Disabled} |
||||
* and {@code @DisabledIf("false")} is logically meaningless. |
||||
* |
||||
* @see #reason |
||||
* @see #value |
||||
*/ |
||||
@AliasFor("value") |
||||
String expression() default ""; |
||||
|
||||
/** |
||||
* The reason this test is disabled. |
||||
* |
||||
* @see #expression |
||||
*/ |
||||
String reason() default ""; |
||||
|
||||
} |
||||
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
/* |
||||
* Copyright 2002-2016 the original author or authors. |
||||
* |
||||
* 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 org.springframework.test.context.junit.jupiter; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.AnnotatedElement; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
|
||||
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.ExtensionContext; |
||||
import org.junit.jupiter.api.extension.TestExecutionCondition; |
||||
import org.junit.jupiter.api.extension.TestExtensionContext; |
||||
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext; |
||||
import org.springframework.beans.factory.config.BeanExpressionResolver; |
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.core.annotation.AnnotatedElementUtils; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* {@code DisabledIfCondition} is a composite {@link ContainerExecutionCondition} |
||||
* and {@link TestExecutionCondition} that supports the {@link DisabledIf @DisabledIf} |
||||
* annotation when using the <em>Spring TestContext Framework</em> in conjunction |
||||
* with JUnit 5's <em>Jupiter</em> programming model. |
||||
* |
||||
* <p>Any attempt to use {@code DisabledIfCondition} without the presence of |
||||
* {@link DisabledIf @DisabledIf} will result in an {@link IllegalStateException}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @author Tadaya Tsuyukubo |
||||
* @since 5.0 |
||||
* @see org.springframework.test.context.junit.jupiter.DisabledIf |
||||
* @see org.springframework.test.context.junit.jupiter.SpringExtension |
||||
*/ |
||||
public class DisabledIfCondition implements ContainerExecutionCondition, TestExecutionCondition { |
||||
|
||||
private static final Log logger = LogFactory.getLog(DisabledIfCondition.class); |
||||
|
||||
|
||||
/** |
||||
* Containers are disabled if {@code @DisabledIf} is present on the test class
|
||||
* and the configured expression evaluates to {@code true}. |
||||
*/ |
||||
@Override |
||||
public ConditionEvaluationResult evaluate(ContainerExtensionContext context) { |
||||
return evaluateDisabledIf(context); |
||||
} |
||||
|
||||
/** |
||||
* Tests are disabled if {@code @DisabledIf} is present on the test method |
||||
* and the configured expression evaluates to {@code true}. |
||||
*/ |
||||
@Override |
||||
public ConditionEvaluationResult evaluate(TestExtensionContext context) { |
||||
return evaluateDisabledIf(context); |
||||
} |
||||
|
||||
private ConditionEvaluationResult evaluateDisabledIf(ExtensionContext extensionContext) { |
||||
AnnotatedElement element = extensionContext.getElement().get(); |
||||
Optional<DisabledIf> disabledIf = findMergedAnnotation(element, DisabledIf.class); |
||||
Assert.state(disabledIf.isPresent(), () -> "@DisabledIf must be present on " + element); |
||||
|
||||
String expression = disabledIf.get().expression().trim(); |
||||
|
||||
if (isDisabled(expression, extensionContext)) { |
||||
String reason = disabledIf.map(DisabledIf::reason).filter(StringUtils::hasText).orElseGet( |
||||
() -> String.format("%s is disabled because @DisabledIf(\"%s\") evaluated to true", element, |
||||
expression)); |
||||
logger.info(reason); |
||||
return ConditionEvaluationResult.disabled(reason); |
||||
} |
||||
else { |
||||
String reason = String.format("%s is enabled because @DisabledIf(\"%s\") did not evaluate to true", |
||||
element, expression); |
||||
logger.debug(reason); |
||||
return ConditionEvaluationResult.enabled(reason); |
||||
} |
||||
} |
||||
|
||||
private boolean isDisabled(String expression, ExtensionContext extensionContext) { |
||||
ApplicationContext applicationContext = SpringExtension.getApplicationContext(extensionContext); |
||||
|
||||
if (!(applicationContext instanceof ConfigurableApplicationContext)) { |
||||
if (logger.isWarnEnabled()) { |
||||
String contextType = (applicationContext != null ? applicationContext.getClass().getName() : "null"); |
||||
logger.warn(String.format("@DisabledIf(\"%s\") could not be evaluated on [%s] since the test " + |
||||
"ApplicationContext [%s] is not a ConfigurableApplicationContext", |
||||
expression, extensionContext.getElement(), contextType)); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
ConfigurableBeanFactory configurableBeanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); |
||||
BeanExpressionResolver expressionResolver = configurableBeanFactory.getBeanExpressionResolver(); |
||||
BeanExpressionContext beanExpressionContext = new BeanExpressionContext(configurableBeanFactory, null); |
||||
|
||||
Object result = expressionResolver.evaluate(configurableBeanFactory.resolveEmbeddedValue(expression), |
||||
beanExpressionContext); |
||||
|
||||
Assert.state((result instanceof Boolean || result instanceof String), () -> |
||||
String.format("@DisabledIf(\"%s\") must evaluate to a String or a Boolean, not %s", expression, |
||||
(result != null ? result.getClass().getName() : "null"))); |
||||
|
||||
boolean disabled = (result instanceof Boolean && ((Boolean) result).booleanValue()) || |
||||
(result instanceof String && Boolean.parseBoolean((String) result)); |
||||
|
||||
return disabled; |
||||
} |
||||
|
||||
private static <A extends Annotation> Optional<A> findMergedAnnotation(AnnotatedElement element, |
||||
Class<A> annotationType) { |
||||
return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(element, annotationType)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
/* |
||||
* Copyright 2002-2016 the original author or authors. |
||||
* |
||||
* 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 org.springframework.test.context.junit.jupiter; |
||||
|
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.test.context.TestPropertySource; |
||||
|
||||
import static org.junit.jupiter.api.Assertions.*; |
||||
|
||||
/** |
||||
* Integration tests which verify support for {@link DisabledIf @DisabledIf} |
||||
* in conjunction with the {@link SpringExtension} in a JUnit 5 (Jupiter) |
||||
* environment. |
||||
* |
||||
* @author Tadaya Tsuyukubo |
||||
* @author Sam Brannen |
||||
* @since 5.0 |
||||
* @see DisabledIf |
||||
* @see SpringExtension |
||||
*/ |
||||
class DisabledIfTestCase { |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@TestPropertySource(properties = "foo = true") |
||||
@Nested |
||||
class DisabledIfOnMethodTestCase { |
||||
|
||||
@Test |
||||
@DisabledIf("true") |
||||
void disabledByStringTrue() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("TrUe") |
||||
void disabledByStringTrueIgnoreCase() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("${foo}") |
||||
void disabledByPropertyPlaceholder() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("#{T(java.lang.Boolean).TRUE}") |
||||
void disabledBySpelBoolean() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("#{'tr' + 'ue'}") |
||||
void disabledBySpelStringConcatenation() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("#{6 * 7 == 42}") |
||||
void disabledBySpelMathematicalComparison() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledOnMac |
||||
void disabledBySpelOsCheckInCustomComposedAnnotation() { |
||||
assertFalse(System.getProperty("os.name").contains("Mac"), "This test must be disabled on Mac OS"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("#{@booleanTrueBean}") |
||||
void disabledBySpelBooleanTrueBean() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
@Test |
||||
@DisabledIf("#{@stringTrueBean}") |
||||
void disabledBySpelStringTrueBean() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
} |
||||
|
||||
@SpringJUnitConfig(Config.class) |
||||
@Nested |
||||
@DisabledIf("true") |
||||
class DisabledIfOnClassTestCase { |
||||
|
||||
@Test |
||||
void foo() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
// Even though method level condition is not disabling test, class level condition
|
||||
// should take precedence
|
||||
@Test |
||||
@DisabledIf("false") |
||||
void bar() { |
||||
fail("This test must be disabled"); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class Config { |
||||
|
||||
@Bean |
||||
Boolean booleanTrueBean() { |
||||
return Boolean.TRUE; |
||||
} |
||||
|
||||
@Bean |
||||
String stringTrueBean() { |
||||
return "true"; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* |
||||
* Copyright 2002-2016 the original author or authors. |
||||
* |
||||
* 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 org.springframework.test.context.junit.jupiter; |
||||
|
||||
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; |
||||
|
||||
/** |
||||
* Demo <em>composed annotation</em> for {@link DisabledIf @DisabledIf} that |
||||
* disables a test class or test method if the current operating system is |
||||
* Mac OS. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 5.0 |
||||
*/ |
||||
@Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
@DisabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}", reason = "Disabled on Mac OS") |
||||
public @interface DisabledOnMac { |
||||
} |
||||
Loading…
Reference in new issue