diff --git a/build.gradle b/build.gradle
index ca9109cb..ca72c721 100644
--- a/build.gradle
+++ b/build.gradle
@@ -129,6 +129,39 @@ publishing {
'Hamcrest Library',
'A library of Hamcrest matchers - deprecated, please use "hamcrest" instead')
}
+
+ def hamcrestJUnit5TestsProject = project(':hamcrest-junit5-tests')
+ hamcrestJUnit5Tests(MavenPublication) {
+ from hamcrestJUnit5TestsProject.components.java
+ artifactId hamcrestJUnit5TestsProject.name
+ artifact hamcrestJUnit5TestsProject.sourcesJar
+ artifact hamcrestJUnit5TestsProject.javadocJar
+ pom pomConfigurationFor(
+ 'Hamcrest JUnit 5 Tests',
+ 'A test suite for Hamcrest assumptions using JUnit 5')
+ }
+
+ def hamcrestJUnit4TestsProject = project(':hamcrest-junit4-tests')
+ hamcrestJUnit4Tests(MavenPublication) {
+ from hamcrestJUnit4TestsProject.components.java
+ artifactId hamcrestJUnit4TestsProject.name
+ artifact hamcrestJUnit4TestsProject.sourcesJar
+ artifact hamcrestJUnit4TestsProject.javadocJar
+ pom pomConfigurationFor(
+ 'Hamcrest JUnit4 Tests',
+ 'A test suite for Hamcrest assumptions using JUnit 4')
+ }
+
+ def hamcrestJUnit4JUnit5TestsProject = project(':hamcrest-junit4-junit5-tests')
+ hamcrestJUnit4JUnit5Tests(MavenPublication) {
+ from hamcrestJUnit4JUnit5TestsProject.components.java
+ artifactId hamcrestJUnit4JUnit5TestsProject.name
+ artifact hamcrestJUnit4JUnit5TestsProject.sourcesJar
+ artifact hamcrestJUnit4JUnit5TestsProject.javadocJar
+ pom pomConfigurationFor(
+ 'Hamcrest Hybrid JUnit 4/JUnit 5 Tests',
+ 'A test suite for Hamcrest assumptions using hybrid JUnit 4/JUnit 5')
+ }
}
repositories {
if (publishToOssrh) {
@@ -150,4 +183,7 @@ signing {
sign publishing.publications.hamcrest
sign publishing.publications.hamcrestCore
sign publishing.publications.hamcrestLibrary
+ sign publishing.publications.hamcrestJUnit5Tests
+ sign publishing.publications.hamcrestJUnit4Tests
+ sign publishing.publications.hamcrestJUnit4JUnit5Tests
}
diff --git a/hamcrest-junit4-junit5-tests/hamcrest-junit4-junit5-tests.gradle b/hamcrest-junit4-junit5-tests/hamcrest-junit4-junit5-tests.gradle
new file mode 100644
index 00000000..afc5e845
--- /dev/null
+++ b/hamcrest-junit4-junit5-tests/hamcrest-junit4-junit5-tests.gradle
@@ -0,0 +1,20 @@
+plugins {
+ id 'java'
+}
+
+group 'org.hamcrest'
+version '2.3-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation project(':hamcrest')
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
+ testImplementation 'org.junit.vintage:junit-vintage-engine:5.8.2'
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java
new file mode 100644
index 00000000..4bc67403
--- /dev/null
+++ b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java
@@ -0,0 +1,54 @@
+package org.hamcrest;
+
+import org.junit.Test;
+import org.junit.AssumptionViolatedException;
+import org.opentest4j.TestAbortedException;
+
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests compatibility with JUnit 4 and JUnit 5 on the classpath.
+ * The equivalent test with only JUnit 4 on the classpath is in another module.
+ */
+public class JUnit4MatcherAssumeTest {
+
+ @Test public void
+ assumptionFailsWithMessage() {
+ try {
+ assumeThat("Custom assumption", "a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssumptionViolatedException e) {
+ assertEquals("Custom assumption: got: \"a\", expected: a string starting with \"abc\"", e.getMessage());
+ }
+ catch (TestAbortedException e) {
+ throw new AssertionError("Illegal JUnit 5 assumption", e);
+ }
+ }
+
+ @Test public void
+ assumptionFailsWithDefaultMessage() {
+ try {
+ assumeThat("a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssumptionViolatedException e) {
+ assertEquals(": got: \"a\", expected: a string starting with \"abc\"", e.getMessage());
+ }
+ catch (TestAbortedException e) {
+ throw new AssertionError("Illegal JUnit 5 assumption", e);
+ }
+ }
+
+ @Test public void
+ assumptionSucceeds() {
+ try {
+ assumeThat("xyz", startsWith("xy"));
+ } catch (TestAbortedException e) {
+ throw new AssertionError("Illegal JUnit 5 assumption", e);
+ }
+ }
+}
diff --git a/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java
new file mode 100644
index 00000000..cf4929e5
--- /dev/null
+++ b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java
@@ -0,0 +1,62 @@
+package org.hamcrest;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.TestAbortedException;
+
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Tests compatibility with JUnit 5 with JUnit 4 and JUnit 5 on the classpath.
+ * The equivalent test with only JUnit 4 on the classpath is in another module.
+ */
+class JUnit5MatcherAssumeTest {
+
+ @Test
+ void
+ assumptionFailsWithMessage() {
+ try {
+ assumeThat("Custom assumption", "a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (TestAbortedException e) {
+ assertEquals("Assumption failed: Custom assumption", e.getMessage());
+ }
+ catch (AssumptionViolatedException e) {
+ // If we don't catch JUnit 4 exceptions here, then this test will result in a false positive, or actually
+ // a false ignored test.
+ throw new AssertionError("Illegal JUnit 4 assumption", e);
+ }
+ }
+
+ @Test void
+ assumptionFailsWithDefaultMessage() {
+ try {
+ assumeThat("a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (TestAbortedException e) {
+ assertEquals("Assumption failed", e.getMessage());
+ }
+ catch (AssumptionViolatedException e) {
+ // If we don't catch JUnit 4 exceptions here, then this test will result in a false positive, or actually
+ // a false ignored test.
+ throw new AssertionError("Illegal JUnit 4 assumption", e);
+ }
+ }
+
+ @Test void
+ assumptionSucceeds() {
+ try {
+ assumeThat("xyz", startsWith("xy"));
+ }
+ catch (AssumptionViolatedException e) {
+ // If we don't catch JUnit 4 exceptions here, then this test will result in a false positive, or actually
+ // a false ignored test.
+ throw new AssertionError("Illegal JUnit 4 assumption", e);
+ }
+ }
+}
diff --git a/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/MatcherAssumeTest.java b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/MatcherAssumeTest.java
new file mode 100644
index 00000000..3c85efe5
--- /dev/null
+++ b/hamcrest-junit4-junit5-tests/src/test/java/org/hamcrest/MatcherAssumeTest.java
@@ -0,0 +1,39 @@
+package org.hamcrest;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.TestAbortedException;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class MatcherAssumeTest {
+
+ @Test
+ void assumptionFailsWithAssertionErrorWhenNoJUnitInStackTrace() throws Throwable {
+ // Run the assumption on a separate thread to make sure it has JUnit 4 nor JUnit 5 in its stack trace.
+ ExecutorService executor = newSingleThreadExecutor();
+ try {
+ try {
+ executor.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ assumeThat(1, is(2));
+ }
+ }).get();
+ fail("Expected " + ExecutionException.class);
+ } catch (ExecutionException expected) {
+ throw expected.getCause();
+ }
+ } catch (AssertionError expected) {
+ } catch (TestAbortedException | AssumptionViolatedException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/hamcrest-junit4-tests/hamcrest-junit4-tests.gradle b/hamcrest-junit4-tests/hamcrest-junit4-tests.gradle
new file mode 100644
index 00000000..c1f34af0
--- /dev/null
+++ b/hamcrest-junit4-tests/hamcrest-junit4-tests.gradle
@@ -0,0 +1,17 @@
+dependencies {
+ testImplementation project(':hamcrest')
+ testImplementation(group: 'junit', name: 'junit', version: '4.13.2') {
+ transitive = false
+ }
+}
+
+jar {
+ manifest {
+ attributes 'Implementation-Title': project.name,
+ 'Implementation-Vendor': 'hamcrest.org',
+ 'Implementation-Version': version,
+ 'Automatic-Module-Name': 'org.hamcrest.junit4-tests'
+ }
+}
+
+javadoc.title = "Hamcrest JUnit 4 Tests $version"
diff --git a/hamcrest-junit4-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java b/hamcrest-junit4-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java
new file mode 100644
index 00000000..a0e0adb4
--- /dev/null
+++ b/hamcrest-junit4-tests/src/test/java/org/hamcrest/JUnit4MatcherAssumeTest.java
@@ -0,0 +1,43 @@
+package org.hamcrest;
+
+import org.junit.Test;
+import org.junit.AssumptionViolatedException;
+
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests compatibility with JUnit 4 with only JUnit 4 on the classpath.
+ * The equivalent test with JUnit 4 and JUnit 5 on the classpath is in another module.
+ */
+public class JUnit4MatcherAssumeTest {
+
+ @Test public void
+ assumptionFailsWithMessage() {
+ try {
+ assumeThat("Custom assumption", "a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssumptionViolatedException e) {
+ assertEquals("Custom assumption: got: \"a\", expected: a string starting with \"abc\"", e.getMessage());
+ }
+ }
+
+ @Test public void
+ assumptionFailsWithDefaultMessage() {
+ try {
+ assumeThat("a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssumptionViolatedException e) {
+ assertEquals(": got: \"a\", expected: a string starting with \"abc\"", e.getMessage());
+ }
+ }
+
+ @Test public void
+ assumptionSucceeds() {
+ assumeThat("xyz", startsWith("xy"));
+ }
+}
diff --git a/hamcrest-junit5-tests/hamcrest-junit5-tests.gradle b/hamcrest-junit5-tests/hamcrest-junit5-tests.gradle
new file mode 100644
index 00000000..388b8ab0
--- /dev/null
+++ b/hamcrest-junit5-tests/hamcrest-junit5-tests.gradle
@@ -0,0 +1,21 @@
+dependencies {
+ api project(':hamcrest')
+ api 'org.opentest4j:opentest4j:1.2.0'
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
+}
+
+jar {
+ manifest {
+ attributes 'Implementation-Title': project.name,
+ 'Implementation-Vendor': 'hamcrest.org',
+ 'Implementation-Version': version,
+ 'Automatic-Module-Name': 'org.hamcrest.junit5-tests'
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
+
+javadoc.title = "Hamcrest JUnit 5 Tests $version"
diff --git a/hamcrest-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java b/hamcrest-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java
new file mode 100644
index 00000000..12f5ddb9
--- /dev/null
+++ b/hamcrest-junit5-tests/src/test/java/org/hamcrest/JUnit5MatcherAssumeTest.java
@@ -0,0 +1,44 @@
+package org.hamcrest;
+
+import org.junit.jupiter.api.Test;
+import org.opentest4j.TestAbortedException;
+
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Tests compatibility with JUnit 5 with only JUnit 5 on the classpath.
+ * The equivalent test with JUnit 4 and JUnit 5 on the classpath is in another module.
+ */
+class JUnit5MatcherAssumeTest {
+
+ @Test
+ void
+ assumptionFailsWithMessage() {
+ try {
+ assumeThat("Custom assumption", "a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (TestAbortedException e) {
+ assertEquals("Assumption failed: Custom assumption", e.getMessage());
+ }
+ }
+
+ @Test void
+ assumptionFailsWithDefaultMessage() {
+ try {
+ assumeThat("a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (TestAbortedException e) {
+ assertEquals("Assumption failed", e.getMessage());
+ }
+ }
+
+ @Test void
+ assumptionSucceeds() {
+ assumeThat("xyz", startsWith("xy"));
+ }
+}
diff --git a/hamcrest/hamcrest.gradle b/hamcrest/hamcrest.gradle
index 749b7255..c2b35ccd 100644
--- a/hamcrest/hamcrest.gradle
+++ b/hamcrest/hamcrest.gradle
@@ -3,7 +3,9 @@ apply plugin: 'osgi'
version = rootProject.version
dependencies {
- testImplementation(group: 'junit', name: 'junit', version: '4.13') {
+ compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
+ compileOnly 'junit:junit:4.13.2'
+ testImplementation(group: 'junit', name: 'junit', version: '4.13.2') {
transitive = false
}
}
diff --git a/hamcrest/src/main/java/org/hamcrest/MatcherAssume.java b/hamcrest/src/main/java/org/hamcrest/MatcherAssume.java
new file mode 100644
index 00000000..bcd7fa65
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/MatcherAssume.java
@@ -0,0 +1,17 @@
+package org.hamcrest;
+
+import org.hamcrest.internal.AssumptionProvider;
+
+public final class MatcherAssume {
+
+ private MatcherAssume() {
+ }
+
+ public static void assumeThat(T actual, Matcher super T> matcher) {
+ assumeThat("", actual, matcher);
+ }
+
+ public static void assumeThat(String message, T actual, Matcher super T> matcher) {
+ AssumptionProvider.getInstance().assumeThat(message, actual, matcher);
+ }
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/internal/AssertionAssumptionProvider.java b/hamcrest/src/main/java/org/hamcrest/internal/AssertionAssumptionProvider.java
new file mode 100644
index 00000000..0f4feaf6
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/internal/AssertionAssumptionProvider.java
@@ -0,0 +1,24 @@
+package org.hamcrest.internal;
+
+import org.hamcrest.Matcher;
+
+/**
+ * The default (fallback) assumption provider throws {@link AssertionError}, by lack of anything better.
+ */
+class AssertionAssumptionProvider extends AssumptionProvider {
+
+ @Override
+ public void assumeThat(String message, T actual, Matcher matcher) {
+ // Java only knows assertions, not assumptions.
+ // By lack of anything better, we treat an assumption as an assertion by default.
+ // Alternatively, we could just do nothing, but that would result in tests failing for the wrong reason,
+ // because their assumptions would silently fail, which is worse.
+ if (!matcher.matches(actual)) {
+ throw new AssertionError(isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed");
+ }
+ }
+
+ private static boolean isNotBlank(String string) {
+ return string != null && !string.trim().isEmpty();
+ }
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/internal/AssumptionProvider.java b/hamcrest/src/main/java/org/hamcrest/internal/AssumptionProvider.java
new file mode 100644
index 00000000..3d9640b7
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/internal/AssumptionProvider.java
@@ -0,0 +1,85 @@
+package org.hamcrest.internal;
+
+import org.hamcrest.Matcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.Thread.currentThread;
+
+/**
+ * A facade for assumption failures that uses the Java services framework to let implementations plug in their own
+ * implementations.
+ * Java does not define an assumption failure like it does {@link AssertionError} for an assertion failure.
+ * Therefore, testing frameworks such as JUnit 4 and JUnit 5 (OpenTest4J) define their own.
+ * This class lets the different test frameworks to plug in their own assumption failures.
+ */
+public abstract class AssumptionProvider {
+
+ private static final AssumptionProvider INSTANCE = new CompositeAssumptionProvider(loadAssumptionProviders());
+
+ /**
+ * Assumes that an object is matching a matcher, throwing on violation of that assumption.
+ *
+ * @param message a violation message for when the assumption is violated, which may be {@code null}.
+ * @param actual an object to match, which may be {@code null}.
+ * @param matcher a matcher to match an object with, which must not be {@code null}.
+ * @param an object's generic type.
+ */
+ public abstract void assumeThat(String message, T actual, Matcher matcher);
+
+ /**
+ * Gets the singleton instance assumption provider, which combines 3 assumption providers:
+ *
+ * - An optional assumption provider for JUnit 5, only if the runtime classpath contains JUnit 5.
+ * - An optional assumption provider for JUnit 4, only if the runtime classpath contains JUnit 4.
+ * - A default assumption provider which throws {@link AssertionError} on violation.
+ *
+ *
+ * @return the singleton assumption provider, never {@code null}.
+ */
+ public static AssumptionProvider getInstance() {
+ return INSTANCE;
+ }
+
+ private static List loadAssumptionProviders() {
+ ArrayList providers = new ArrayList<>(3);
+ try {
+ providers.add(new JUnit5AssumptionProvider());
+ } catch (NoClassDefFoundError ignored) {
+ // Optional JUnit 5 dependency is not on runtime class path. Continue without it.
+ }
+ try {
+ providers.add(new JUnit4AssumptionProvider());
+ } catch (NoClassDefFoundError ignored) {
+ // Optional JUnit 4 dependency is not on runtime class path. Continue without it.
+ }
+ providers.add(new AssertionAssumptionProvider());
+ providers.trimToSize();
+ return providers;
+ }
+
+ /**
+ * Searches the stack trace top-down for an occurrence of a package name prefix to reject or accept.
+ * If the first hit is to reject, then this method returns {@code false}.
+ * If the first hit is to accept, then this method returns {@code true}.
+ * If there is no hit at all, then this method returns {@code false}.
+ *
+ * @param packageNamePrefixToReject a package name prefix to reject, which must not be {@code null}.
+ * @param packageNamePrefixToAccept a package name prefix to accept, which must not be {@code null}.
+ * @return {@code true} if the stack trace contains an entry matching the package name prefix to accept before it
+ * contains that to reject, {@code false} otherwise.
+ */
+ protected static boolean stackTraceContains(String packageNamePrefixToReject, String packageNamePrefixToAccept) {
+ StackTraceElement[] stackTrace = currentThread().getStackTrace();
+ for (int i = stackTrace.length; --i > -1; ) {
+ StackTraceElement stackTraceElement = stackTrace[i];
+ if (stackTraceElement.getClassName().startsWith(packageNamePrefixToReject)) {
+ return false;
+ } else if (stackTraceElement.getClassName().startsWith(packageNamePrefixToAccept)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/internal/CompositeAssumptionProvider.java b/hamcrest/src/main/java/org/hamcrest/internal/CompositeAssumptionProvider.java
new file mode 100644
index 00000000..f09cea7b
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/internal/CompositeAssumptionProvider.java
@@ -0,0 +1,21 @@
+package org.hamcrest.internal;
+
+import org.hamcrest.Matcher;
+
+import java.util.List;
+
+class CompositeAssumptionProvider extends AssumptionProvider {
+
+ final List providers;
+
+ CompositeAssumptionProvider(List providers) {
+ this.providers = providers;
+ }
+
+ @Override
+ public void assumeThat(String message, T actual, Matcher matcher) {
+ for (AssumptionProvider provider : providers) {
+ provider.assumeThat(message, actual, matcher);
+ }
+ }
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/internal/JUnit4AssumptionProvider.java b/hamcrest/src/main/java/org/hamcrest/internal/JUnit4AssumptionProvider.java
new file mode 100644
index 00000000..00789821
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/internal/JUnit4AssumptionProvider.java
@@ -0,0 +1,22 @@
+package org.hamcrest.internal;
+
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+
+/**
+ * This class provides assumption violations compatible with JUnit 4.
+ */
+class JUnit4AssumptionProvider extends AssumptionProvider {
+
+ static {
+ // Trigger NoClassDefFoundError when JUnit 4 not on classpath.
+ Assume.class.getName();
+ }
+
+ @Override
+ public void assumeThat(String message, T actual, Matcher matcher) {
+ if (stackTraceContains("org.junit.jupiter.", "org.junit.runners.")) {
+ Assume.assumeThat(message, actual, matcher);
+ }
+ }
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/internal/JUnit5AssumptionProvider.java b/hamcrest/src/main/java/org/hamcrest/internal/JUnit5AssumptionProvider.java
new file mode 100644
index 00000000..3e21f4d9
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/internal/JUnit5AssumptionProvider.java
@@ -0,0 +1,24 @@
+package org.hamcrest.internal;
+
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Assumptions;
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * This class provides assumption violations compatible with JUnit 5.
+ */
+class JUnit5AssumptionProvider extends AssumptionProvider {
+
+ static {
+ // Trigger NoClassDefFoundError when JUnit 5 not on classpath.
+ Assumptions.class.getName();
+ }
+
+ @Override
+ public void assumeThat(String message, T actual, Matcher matcher) {
+ if (stackTraceContains("org.junit.runners.", "org.junit.jupiter.")) {
+ assumeTrue(matcher.matches(actual), message);
+ }
+ }
+}
diff --git a/hamcrest/src/test/java/org/hamcrest/MatcherAssumeTest.java b/hamcrest/src/test/java/org/hamcrest/MatcherAssumeTest.java
new file mode 100644
index 00000000..1955ba28
--- /dev/null
+++ b/hamcrest/src/test/java/org/hamcrest/MatcherAssumeTest.java
@@ -0,0 +1,37 @@
+package org.hamcrest;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssume.assumeThat;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.*;
+
+public class MatcherAssumeTest {
+
+ @Test public void
+ assumptionFailsWithMessage() {
+ try {
+ assumeThat("Custom assumption", "a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssertionError e) {
+ assertEquals("Assumption failed: Custom assumption", e.getMessage());
+ }
+ }
+
+ @Test public void
+ assumptionFailsWithDefaultMessage() {
+ try {
+ assumeThat("a", startsWith("abc"));
+ fail("should have failed");
+ }
+ catch (AssertionError e) {
+ assertEquals("Assumption failed", e.getMessage());
+ }
+ }
+
+ @Test public void
+ assumptionSucceeds() {
+ assumeThat("xyz", startsWith("xy"));
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index b2cd43d7..b5dc4694 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,9 @@
enableFeaturePreview('STABLE_PUBLISHING')
include 'hamcrest',
+ 'hamcrest-junit5-tests',
+ 'hamcrest-junit4-tests',
+ 'hamcrest-junit4-junit5-tests',
'hamcrest-core',
'hamcrest-library',
'hamcrest-integration'