You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by re...@apache.org on 2020/09/07 06:58:26 UTC

[uima-uimafit] 01/01: [UIMA-6263] CAS validation support

This is an automated email from the ASF dual-hosted git repository.

rec pushed a commit to branch UIMA-6263-CAS-validation-support
in repository https://gitbox.apache.org/repos/asf/uima-uimafit.git

commit 97a260d04f8c4cc37c583ba0079d3238d49ac02b
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Mon Sep 7 08:58:15 2020 +0200

    [UIMA-6263] CAS validation support
    
    - Initial version of the CAS validation framework
    - Added a JUnit and AssertJ modules
---
 README                                             |   3 +
 pom.xml                                            |  12 ++
 uimafit-assertj/pom.xml                            |  41 +++++++
 .../testing/assertj/CasValidationCheckAssert.java  |  60 ++++++++++
 .../uima/fit/validation/CasValidationCheck.java    |  42 +++++++
 .../uima/fit/validation/CasValidationResult.java   | 121 +++++++++++++++++++
 .../uima/fit/validation/CasValidationSummary.java  |  54 +++++++++
 .../apache/uima/fit/validation/CasValidator.java   | 130 +++++++++++++++++++++
 .../fit/validation/CasValidatorBuilderTest.java    |  60 ++++++++++
 .../checks/EndAfterBeginCheckForTesting.java       |  51 ++++++++
 .../checks/EndSameAsBeginCheckForTesting.java      |  51 ++++++++
 ...g.apache.uima.fit.validation.CasValidationCheck |   2 +
 uimafit-junit/pom.xml                              |  45 +++++++
 .../org/apache/uima/fit/testing/junit/CasRule.java |  78 +++++++++++++
 uimafit-parent/pom.xml                             |   3 +
 15 files changed, 753 insertions(+)

diff --git a/README b/README
index 30a74d7..59e67fb 100644
--- a/README
+++ b/README
@@ -95,6 +95,9 @@ uimafit-core           - the main uimaFIT module
 uimafit-cpe            - support for the Collection Processing Engine (multi-threaded pipelines)
 uimafit-maven          - a Maven plugin to automatically enhance UIMA components with uimaFIT
                          metadata and to generate XML descriptors for uimaFIT-enabled components.
+uimafit-junit          - convenience code facilitating the implementation of UIMA/uimaFIT tests
+                         in JUnit tests
+uimafit-assertj        - adds assertions for UIMA/uimaFIT types via the AssertJ framework
 uimafit-legacy-support - allows uimaFIT 2.x.0 to use uimaFIT 1.4.x meta data like Java annotations
                          and META-INF/org.uimafit/types.txt files. Pipelines mixing uimaFIT 1.4.x
                          and 2.x components MUST be created using the 2.x factories, because the
diff --git a/pom.xml b/pom.xml
index ea84744..75e6e08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,16 @@
     </dependency>
     <dependency>
       <groupId>org.apache.uima</groupId>
+      <artifactId>uimafit-junit</artifactId>
+      <version>2.5.1-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.uima</groupId>
+      <artifactId>uimafit-assertj</artifactId>
+      <version>2.5.1-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.uima</groupId>
       <artifactId>uimafit-legacy-support</artifactId>
       <version>2.5.1-SNAPSHOT</version>
     </dependency>
@@ -124,6 +134,8 @@
 
   <modules>
     <module>uimafit-core</module>
+    <module>uimafit-junit</module>
+    <module>uimafit-assertj</module>
     <module>uimafit-examples</module>
     <module>uimafit-spring</module>
     <module>uimafit-maven-plugin</module>
diff --git a/uimafit-assertj/pom.xml b/uimafit-assertj/pom.xml
new file mode 100644
index 0000000..8f888a0
--- /dev/null
+++ b/uimafit-assertj/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	Licensed to the Apache Software Foundation (ASF) under one
+	or more contributor license agreements. See the NOTICE file
+	distributed with this work for additional information
+	regarding copyright ownership. The ASF licenses this file
+	to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.uima</groupId>
+		<artifactId>uimafit-parent</artifactId>
+		<version>2.5.1-SNAPSHOT</version>
+		<relativePath>../uimafit-parent</relativePath>
+	</parent>
+	<artifactId>uimafit-assertj</artifactId>
+	<name>Apache UIMA uimaFIT - AssertJ support</name>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.uima</groupId>
+			<artifactId>uimafit-core</artifactId>
+			<version>2.5.1-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+		  <groupId>org.assertj</groupId>
+		  <artifactId>assertj-core</artifactId>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/uimafit-assertj/src/main/java/org/apache/uima/fit/testing/assertj/CasValidationCheckAssert.java b/uimafit-assertj/src/main/java/org/apache/uima/fit/testing/assertj/CasValidationCheckAssert.java
new file mode 100644
index 0000000..dee603a
--- /dev/null
+++ b/uimafit-assertj/src/main/java/org/apache/uima/fit/testing/assertj/CasValidationCheckAssert.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.testing.assertj;
+
+import static java.util.ServiceLoader.load;
+import static java.util.stream.StreamSupport.stream;
+
+import java.util.ServiceLoader;
+
+import org.apache.uima.fit.validation.CasValidationCheck;
+import org.assertj.core.api.AbstractAssert;
+
+/**
+ * Asserts related to {@link CasValidationCheck} implementations.
+ */
+public class CasValidationCheckAssert
+        extends AbstractAssert<CasValidationCheckAssert, CasValidationCheck> {
+  public CasValidationCheckAssert(CasValidationCheck actual) {
+    super(actual, CasValidationCheckAssert.class);
+  }
+
+  public static CasValidationCheckAssert assertThat(CasValidationCheck actual) {
+    return new CasValidationCheckAssert(actual);
+  }
+
+  /**
+   * Checks that the check is correctly registered and available to the Java Service Locator.
+   */
+  public CasValidationCheckAssert isAvailableToServiceLoader() {
+    isNotNull();
+
+    ServiceLoader<CasValidationCheck> loader = load(CasValidationCheck.class);
+    boolean found = stream(loader.spliterator(), false)
+            .anyMatch(check -> check.getClass().equals(actual.getClass()));
+
+    if (!found) {
+      failWithMessage(
+              "[%s] cannot be found by the service loader. Ensure it is registered in [META-INF/services/%s]",
+              actual.getClass(), CasValidationCheck.class.getName());
+    }
+
+    return this;
+  }
+}
\ No newline at end of file
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationCheck.java b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationCheck.java
new file mode 100644
index 0000000..02c07ab
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationCheck.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation;
+
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+
+/**
+ * CAS validation check.
+ * <p>
+ * <b>Note:</b> Implementations of this class are typically singletons which are obtained through
+ * the Java Service Locator mechanism. This means that the implementations must be stateless to
+ * ensure that they can be used by multiple threads concurrently.
+ */
+public interface CasValidationCheck {
+
+  /**
+   * Apply this check to the given CAS.
+   * 
+   * @param cas
+   *          the CAS to check.
+   * @return the results of the check.
+   */
+  List<CasValidationResult> check(CAS cas);
+}
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationResult.java b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationResult.java
new file mode 100644
index 0000000..9802b1d
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationResult.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation;
+
+import static java.lang.String.format;
+import static org.apache.uima.fit.validation.CasValidationResult.Severity.DEBUG;
+import static org.apache.uima.fit.validation.CasValidationResult.Severity.ERROR;
+import static org.apache.uima.fit.validation.CasValidationResult.Severity.INFO;
+import static org.apache.uima.fit.validation.CasValidationResult.Severity.TRACE;
+import static org.apache.uima.fit.validation.CasValidationResult.Severity.WARN;
+
+import java.util.Objects;
+
+/**
+ * Individual result from a CAS validation check.
+ */
+public class CasValidationResult {
+
+  public enum Severity {
+
+    ERROR(5), WARN(4), INFO(3), DEBUG(2), TRACE(1);
+
+    private final int level;
+
+    private Severity(int level) {
+      this.level = level;
+    }
+
+    public boolean isEquallyOrMoreSevereThan(Severity other) {
+      return level >= other.level;
+    }
+  }
+
+  private final Severity severity;
+  private final String source;
+  private final String message;
+
+  public CasValidationResult(Object source, Severity severity, String format, Object... args) {
+
+    super();
+
+    if (source instanceof String) {
+      this.source = (String) source;
+    } else if (source instanceof Class) {
+      this.source = ((Class<?>) source).getSimpleName();
+    } else {
+      this.source = source != null ? source.getClass().getSimpleName() : null;
+    }
+
+    this.severity = severity;
+    message = format(format, args);
+  }
+
+  public String getSource() {
+    return source;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public Severity getSeverity() {
+    return severity;
+  }
+
+  public static CasValidationResult error(Object source, String format, Object... args) {
+    return new CasValidationResult(source, ERROR, format, args);
+  }
+
+  public static CasValidationResult warn(Object source, String format, Object... args) {
+    return new CasValidationResult(source, WARN, format, args);
+  }
+
+  public static CasValidationResult info(Object source, String format, Object... args) {
+    return new CasValidationResult(source, INFO, format, args);
+  }
+
+  public static CasValidationResult debug(Object source, String format, Object... args) {
+    return new CasValidationResult(source, DEBUG, format, args);
+  }
+
+  public static CasValidationResult trace(Object source, String format, Object... args) {
+    return new CasValidationResult(source, TRACE, format, args);
+  }
+
+  @Override
+  public boolean equals(final Object other) {
+    if (!(other instanceof CasValidationResult)) {
+      return false;
+    }
+    CasValidationResult castOther = (CasValidationResult) other;
+    return Objects.equals(severity, castOther.severity) && Objects.equals(source, castOther.source)
+            && Objects.equals(message, castOther.message);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(severity, source, message);
+  }
+
+  @Override
+  public String toString() {
+    return String.format("[%s] %s", source != null ? source : "<unknown>", message);
+  }
+}
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationSummary.java b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationSummary.java
new file mode 100644
index 0000000..4d064b0
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidationSummary.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.uima.fit.validation.CasValidationResult.Severity;
+
+/**
+ * Container for {@link CasValidationResult CasValidationResults}.
+ */
+public class CasValidationSummary {
+
+  private List<CasValidationResult> results;
+
+  public CasValidationSummary() {
+    results = new ArrayList<>();
+  }
+
+  public void addAll(CasValidationResult result) {
+    results.add(result);
+  }
+
+  public void addAll(Collection<CasValidationResult> moreResults) {
+    results.addAll(moreResults);
+  }
+
+  public List<CasValidationResult> getResults() {
+    return results;
+  }
+
+  public boolean containsResultsEquallyOrMoreSevereThan(Severity threshold) {
+    return results.stream()
+            .anyMatch(result -> result.getSeverity().isEquallyOrMoreSevereThan(threshold));
+  }
+}
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidator.java b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidator.java
new file mode 100644
index 0000000..76898a5
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/validation/CasValidator.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation;
+
+import static java.util.Arrays.stream;
+import static java.util.ServiceLoader.load;
+import static java.util.stream.StreamSupport.stream;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.uima.cas.CAS;
+
+/**
+ * Validate a CAS.
+ */
+public class CasValidator {
+
+  private Collection<CasValidationCheck> checks;
+
+  public CasValidator(Collection<CasValidationCheck> checks) {
+    this.checks = checks;
+  }
+
+  public CasValidationSummary check(CAS cas) {
+    CasValidationSummary summary = new CasValidationSummary();
+
+    for (CasValidationCheck check : checks) {
+      summary.addAll(check.check(cas));
+    }
+
+    return summary;
+  }
+
+  public Collection<CasValidationCheck> getChecks() {
+    return checks;
+  }
+
+  public static class Builder {
+
+    private Set<CasValidationCheck> checks = new LinkedHashSet<>();
+    private Set<Pattern> excludePatterns = new HashSet<>();
+    private Set<Class<?>> excludeTypes = new HashSet<>();
+    private boolean skipAutoDetection = false;
+
+    /**
+     * Add the given check instance to the validator. This allows even adding checks which are not
+     * available via the Java Service Locator, which take parameters or which are otherwise stateful
+     * (assuming that the resulting validator is not shared between threads).
+     * 
+     * @param check
+     *          a check instance to use.
+     */
+    public void withCheck(CasValidationCheck check) {
+      checks.add(check);
+    }
+
+    public void withoutAutoDetectedChecks() {
+      skipAutoDetection = true;
+    }
+
+    public void witAutoDetectedChecks() {
+      skipAutoDetection = false;
+    }
+
+    /**
+     * Skip auto-detection of any checks with the given names. Subtypes of the given classes are not
+     * excluded from auto-detection.
+     */
+    public void excludingFromAutoDetectionByName(String... className) {
+      stream(className)
+          .map(Pattern::quote)
+          .map(Pattern::compile)
+          .forEach(excludePatterns::add);
+    }
+
+    /**
+     * Skip auto-detection of any checks with the given regular expressions.
+     */
+    public void excludingFromAutoDetectionByPattern(String... patterns) {
+      stream(patterns)
+          .map(Pattern::compile)
+          .forEach(excludePatterns::add);
+    }
+
+    /**
+     * Skips auto-detection of any checks of the given types (includes checks that are subclasses or
+     * implementations of the given types).
+     */
+    public void excludingFromAutoDetectionByType(Class<?>... classes) {
+      stream(classes).forEach(excludeTypes::add);
+    }
+
+    private void autoDetectChecks() {
+      stream(load(CasValidationCheck.class).spliterator(), false)
+              .filter(check -> excludePatterns.stream()
+                      .noneMatch(p -> p.matcher(check.getClass().getName()).matches()))
+              .filter(check -> excludeTypes.stream()
+                      .noneMatch(t -> t.isAssignableFrom(check.getClass())))
+              .forEachOrdered(checks::add);
+    }
+
+    public CasValidator build() {
+      if (!skipAutoDetection) {
+        autoDetectChecks();
+      }
+
+      return new CasValidator(checks);
+    }
+  }
+}
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/validation/CasValidatorBuilderTest.java b/uimafit-core/src/test/java/org/apache/uima/fit/validation/CasValidatorBuilderTest.java
new file mode 100644
index 0000000..82e857a
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/validation/CasValidatorBuilderTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.uima.fit.validation.checks.EndAfterBeginCheckForTesting;
+import org.apache.uima.fit.validation.checks.EndSameAsBeginCheckForTesting;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CasValidatorBuilderTest {
+
+  private CasValidator.Builder sut;
+
+  @Before
+  public void setup() {
+    sut = new CasValidator.Builder();
+  }
+
+  @Test
+  @SuppressWarnings({ "unchecked", "rawtypes" })
+  public void thatExcludeByNameWorks() {
+    sut.excludingFromAutoDetectionByName(EndAfterBeginCheckForTesting.class.getName());
+
+    CasValidator validator = sut.build();
+
+    assertThat(validator.getChecks())
+            .extracting(Object::getClass)
+            .containsExactly((Class) EndSameAsBeginCheckForTesting.class);
+  }
+
+  @Test
+  @SuppressWarnings({ "unchecked", "rawtypes" })
+  public void thatExcludeByTypeWorks() {
+    sut.excludingFromAutoDetectionByType(EndAfterBeginCheckForTesting.class);
+
+    CasValidator validator = sut.build();
+
+    assertThat(validator.getChecks())
+            .extracting(Object::getClass)
+            .containsExactly((Class) EndSameAsBeginCheckForTesting.class);
+  }
+}
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndAfterBeginCheckForTesting.java b/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndAfterBeginCheckForTesting.java
new file mode 100644
index 0000000..f19ed94
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndAfterBeginCheckForTesting.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation.checks;
+
+import static org.apache.uima.fit.util.CasUtil.selectAll;
+import static org.apache.uima.fit.validation.CasValidationResult.error;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.fit.validation.CasValidationCheck;
+import org.apache.uima.fit.validation.CasValidationResult;
+
+/**
+ * Simple CAS validation check ensuring that annotations do not end before they start.
+ * <p>
+ * <b>Note:</b> This is used for testing in uimaFIT. It is not meant for general use!
+ */
+public class EndAfterBeginCheckForTesting implements CasValidationCheck {
+  @Override
+  public List<CasValidationResult> check(CAS cas) {
+    List<CasValidationResult> results = new ArrayList<>();
+
+    for (AnnotationFS anno : selectAll(cas)) {
+      if (anno.getEnd() < anno.getBegin()) {
+        results.add(error(this, "%s ends (%d) before it begins (%d)", anno.getType().getShortName(),
+                anno.getEnd(), anno.getBegin()));
+      }
+    }
+
+    return results;
+  }
+}
diff --git a/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndSameAsBeginCheckForTesting.java b/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndSameAsBeginCheckForTesting.java
new file mode 100644
index 0000000..ac1f29c
--- /dev/null
+++ b/uimafit-core/src/test/java/org/apache/uima/fit/validation/checks/EndSameAsBeginCheckForTesting.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.validation.checks;
+
+import static org.apache.uima.fit.util.CasUtil.selectAll;
+import static org.apache.uima.fit.validation.CasValidationResult.error;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.fit.validation.CasValidationCheck;
+import org.apache.uima.fit.validation.CasValidationResult;
+
+/**
+ * Simple CAS validation check ensuring that annotations do not have the same start/end position.
+ * <p>
+ * <b>Note:</b> This is used for testing in uimaFIT. It is not meant for general use!
+ */
+public class EndSameAsBeginCheckForTesting implements CasValidationCheck {
+  @Override
+  public List<CasValidationResult> check(CAS cas) {
+    List<CasValidationResult> results = new ArrayList<>();
+
+    for (AnnotationFS anno : selectAll(cas)) {
+      if (anno.getEnd() == anno.getBegin()) {
+        results.add(error(this, "%s starts and ends at the same position (%d)",
+                anno.getType().getShortName(), anno.getBegin()));
+      }
+    }
+
+    return results;
+  }
+}
diff --git a/uimafit-core/src/test/resources/META-INF/services/org.apache.uima.fit.validation.CasValidationCheck b/uimafit-core/src/test/resources/META-INF/services/org.apache.uima.fit.validation.CasValidationCheck
new file mode 100644
index 0000000..f52edea
--- /dev/null
+++ b/uimafit-core/src/test/resources/META-INF/services/org.apache.uima.fit.validation.CasValidationCheck
@@ -0,0 +1,2 @@
+org.apache.uima.fit.validation.checks.EndAfterBeginCheckForTesting
+org.apache.uima.fit.validation.checks.EndSameAsBeginCheckForTesting
diff --git a/uimafit-junit/pom.xml b/uimafit-junit/pom.xml
new file mode 100644
index 0000000..1349632
--- /dev/null
+++ b/uimafit-junit/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	Licensed to the Apache Software Foundation (ASF) under one
+	or more contributor license agreements. See the NOTICE file
+	distributed with this work for additional information
+	regarding copyright ownership. The ASF licenses this file
+	to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.uima</groupId>
+		<artifactId>uimafit-parent</artifactId>
+		<version>2.5.1-SNAPSHOT</version>
+		<relativePath>../uimafit-parent</relativePath>
+	</parent>
+	<artifactId>uimafit-junit</artifactId>
+	<name>Apache UIMA uimaFIT - JUnit support</name>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.uima</groupId>
+			<artifactId>uimafit-core</artifactId>
+			<version>2.5.1-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.uima</groupId>
+			<artifactId>uimaj-core</artifactId>
+		</dependency>
+		<dependency>
+		  <groupId>junit</groupId>
+		  <artifactId>junit</artifactId>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/uimafit-junit/src/main/java/org/apache/uima/fit/testing/junit/CasRule.java b/uimafit-junit/src/main/java/org/apache/uima/fit/testing/junit/CasRule.java
new file mode 100644
index 0000000..b4f9a01
--- /dev/null
+++ b/uimafit-junit/src/main/java/org/apache/uima/fit/testing/junit/CasRule.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.uima.fit.testing.junit;
+
+import org.apache.uima.UIMAException;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.fit.factory.CasFactory;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Provides a {@link CAS} object which is automatically reset before the test.
+ */
+public final class CasRule
+    extends TestWatcher
+{
+    private final CAS cas;
+
+    /**
+     * Provides a CAS with an auto-detected type system.
+     */
+    public CasRule()
+    {
+        try {
+            cas = CasFactory.createCas();
+        }
+        catch (UIMAException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Provides a CAS with the specified type system.
+     * 
+     * @param aTypeSystemDescription
+     *            the type system used to initialize the CAS.
+     */
+    public CasRule(TypeSystemDescription aTypeSystemDescription)
+    {
+        try {
+            cas = CasFactory.createCas(aTypeSystemDescription);
+        }
+        catch (UIMAException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @return the CAS object managed by this rule.
+     */
+    public CAS get()
+    {
+        return cas;
+    }
+
+    @Override
+    protected void starting(Description description)
+    {
+        cas.reset();
+    }
+}
\ No newline at end of file
diff --git a/uimafit-parent/pom.xml b/uimafit-parent/pom.xml
index e069ca4..a67d1af 100644
--- a/uimafit-parent/pom.xml
+++ b/uimafit-parent/pom.xml
@@ -173,6 +173,9 @@
         </executions>
         <configuration>
           <failOnWarning>true</failOnWarning>
+          <ignoredDependencies>
+            <ignoredDependency>junit:junit</ignoredDependency>
+          </ignoredDependencies>
         </configuration>
       </plugin>
       <plugin>