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:25 UTC

[uima-uimafit] branch UIMA-6263-CAS-validation-support created (now 97a260d)

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

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


      at 97a260d  [UIMA-6263] CAS validation support

This branch includes the following new commits:

     new 97a260d  [UIMA-6263] CAS validation support

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by re...@apache.org.
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>