diff --git a/dev-tools/eclipse/dot.classpath b/dev-tools/eclipse/dot.classpath
index e940662..33c68ac 100644
--- a/dev-tools/eclipse/dot.classpath
+++ b/dev-tools/eclipse/dot.classpath
@@ -87,7 +87,7 @@
 	<classpathentry kind="src" path="solr/contrib/velocity/src/test-files"/>
 	<classpathentry kind="lib" path="lucene/lib/ant-1.7.1.jar"/>
 	<classpathentry kind="lib" path="lucene/lib/ant-junit-1.7.1.jar"/>
-	<classpathentry kind="lib" path="lucene/lib/junit-4.7.jar"/>
+	<classpathentry kind="lib" path="lucene/lib/junit-4.10.jar"/>
 	<classpathentry kind="lib" path="lucene/contrib/sandbox/lib/jakarta-regexp-1.4.jar"/>
 	<classpathentry kind="lib" path="modules/analysis/icu/lib/icu4j-4_8_1_1.jar"/>
 	<classpathentry kind="lib" path="modules/analysis/phonetic/lib/commons-codec-1.6.jar"/>
@@ -109,7 +109,7 @@
 	<classpathentry kind="lib" path="solr/lib/easymock-2.2.jar"/>
 	<classpathentry kind="lib" path="solr/lib/guava-r05.jar"/>
 	<classpathentry kind="lib" path="solr/lib/jcl-over-slf4j-1.6.1.jar"/>
-	<classpathentry kind="lib" path="solr/lib/junit-4.7.jar"/>
+	<classpathentry kind="lib" path="solr/lib/junit-4.10.jar"/>
 	<classpathentry kind="lib" path="solr/lib/log4j-over-slf4j-1.6.1.jar"/>
 	<classpathentry kind="lib" path="solr/lib/servlet-api-2.4.jar"/>
 	<classpathentry kind="lib" path="solr/lib/slf4j-api-1.6.1.jar"/>
diff --git a/dev-tools/idea/.idea/libraries/JUnit.xml b/dev-tools/idea/.idea/libraries/JUnit.xml
index 4710fc8..f4f4221 100644
--- a/dev-tools/idea/.idea/libraries/JUnit.xml
+++ b/dev-tools/idea/.idea/libraries/JUnit.xml
@@ -1,7 +1,7 @@
 <component name="libraryTable">
   <library name="JUnit">
     <CLASSES>
-      <root url="jar://$PROJECT_DIR$/lucene/lib/junit-4.7.jar!/" />
+      <root url="jar://$PROJECT_DIR$/lucene/lib/junit-4.10.jar!/" />
     </CLASSES>
     <JAVADOC />
     <SOURCES />
diff --git a/lucene/NOTICE.txt b/lucene/NOTICE.txt
index c26b0e2..bd640b1 100644
--- a/lucene/NOTICE.txt
+++ b/lucene/NOTICE.txt
@@ -36,7 +36,7 @@ the Apache CXF project and is Apache License 2.0.
 The Google Code Prettify is Apache License 2.0.
 See http://code.google.com/p/google-code-prettify/
 
-JUnit (under lib/junit-4.7.jar) is licensed under the Common Public License v. 1.0
+JUnit (under lib/junit-4.10.jar) is licensed under the Common Public License v. 1.0
 See http://junit.sourceforge.net/cpl-v10.html
 
 JLine (under contrib/lucli/lib/jline.jar) is licensed under the BSD License.
diff --git a/lucene/common-build.xml b/lucene/common-build.xml
index ce77fd8..67f84c4 100644
--- a/lucene/common-build.xml
+++ b/lucene/common-build.xml
@@ -52,7 +52,7 @@
   <property name="year" value="2000-${current.year}"/>
   <property name="final.name" value="lucene-${name}-${version}"/>
 
-  <property name="junit.jar" value="junit-4.7.jar"/>
+  <property name="junit.jar" value="junit-4.10.jar"/>
   <property name="junit-location.jar" value="${common.dir}/lib/${junit.jar}"/>
   <path id="junit-path">
     <pathelement location="${junit-location.jar}"/>
diff --git a/lucene/contrib/misc/src/java/org/apache/lucene/store/NativePosixUtil.cpp b/lucene/contrib/misc/src/java/org/apache/lucene/store/NativePosixUtil.cpp
index ae8c60a..999e6a2 100644
--- a/lucene/contrib/misc/src/java/org/apache/lucene/store/NativePosixUtil.cpp
+++ b/lucene/contrib/misc/src/java/org/apache/lucene/store/NativePosixUtil.cpp
@@ -34,7 +34,7 @@
 #include <sys/types.h>  // constants for open
 #include <sys/stat.h>  // constants for open
 
-// java -cp .:lib/junit-4.7.jar:./build/classes/test:./build/classes/java:./build/classes/demo -Dlucene.version=2.9-dev -DtempDir=build -ea org.junit.runner.JUnitCore org.apache.lucene.index.TestDoc
+// java -cp .:lib/junit-4.10.jar:./build/classes/test:./build/classes/java:./build/classes/demo -Dlucene.version=2.9-dev -DtempDir=build -ea org.junit.runner.JUnitCore org.apache.lucene.index.TestDoc
 
 #ifdef LINUX
 /*
diff --git a/lucene/core/src/test/org/apache/lucene/index/Test2BTerms.java b/lucene/core/src/test/org/apache/lucene/index/Test2BTerms.java
index 11ebbef..1af4a01 100644
--- a/lucene/core/src/test/org/apache/lucene/index/Test2BTerms.java
+++ b/lucene/core/src/test/org/apache/lucene/index/Test2BTerms.java
@@ -43,7 +43,7 @@ import org.junit.Ignore;
 //
 //   ant test -Dtest.slow=true -Dtests.heapsize=8g
 //
-//   java -server -Xmx8g -d64 -cp .:lib/junit-4.7.jar:./build/classes/test:./build/classes/test-framework:./build/classes/java -Dlucene.version=4.0-dev -Dtests.directory=MMapDirectory -DtempDir=build -ea org.junit.runner.JUnitCore org.apache.lucene.index.Test2BTerms
+//   java -server -Xmx8g -d64 -cp .:lib/junit-4.10.jar:./build/classes/test:./build/classes/test-framework:./build/classes/java -Dlucene.version=4.0-dev -Dtests.directory=MMapDirectory -DtempDir=build -ea org.junit.runner.JUnitCore org.apache.lucene.index.Test2BTerms
 //
 @LuceneTestCase.UseNoMemoryExpensiveCodec
 public class Test2BTerms extends LuceneTestCase {
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestJUnitRuleOrder.java b/lucene/core/src/test/org/apache/lucene/util/TestJUnitRuleOrder.java
new file mode 100644
index 0000000..e4a548f
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/util/TestJUnitRuleOrder.java
@@ -0,0 +1,61 @@
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Stack;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * This verifies that JUnit {@link Rule}s are invoked before 
+ * {@link Before} and {@link  After} hooks. This should be the
+ * case from JUnit 4.10 on.
+ */
+public class TestJUnitRuleOrder {
+  static Stack<String> stack;
+
+  @Before
+  public void before() {
+    stack.push("@Before");
+  }
+  
+  @After
+  public void after() {
+    stack.push("@After");
+  }
+
+  @Rule
+  public TestRule testRule = new TestRule() {
+    @Override
+    public Statement apply(final Statement base, Description description) {
+      return new Statement() {
+        public void evaluate() throws Throwable {
+          stack.push("@Rule before");
+          base.evaluate();
+          stack.push("@Rule after");
+        }
+      };
+    }
+  };
+
+  @Test
+  public void test() {/* empty */}
+
+  @BeforeClass
+  public static void beforeClassCleanup() {
+    stack = new Stack<String>();
+  }
+
+  @AfterClass
+  public static void afterClassCheck() {
+    org.junit.Assert.assertEquals(
+        Arrays.toString(stack.toArray()), "[@Rule before, @Before, @After, @Rule after]");
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestSetupTeardownMethods.java b/lucene/core/src/test/org/apache/lucene/util/TestSetupTeardownMethods.java
new file mode 100644
index 0000000..e00996d
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/util/TestSetupTeardownMethods.java
@@ -0,0 +1,130 @@
+package org.apache.lucene.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+/**
+ * Ensures proper functions of {@link LuceneTestCase#setUp()}
+ * and {@link LuceneTestCase#tearDown()}.
+ */
+public class TestSetupTeardownMethods {
+  
+  public static class NestedSetupChain extends LuceneTestCase {
+    @Override
+    public void setUp() throws Exception {
+      // missing call.
+    }
+    
+    @Test
+    public void testMe() {
+    }
+  }
+
+  public static class NestedTeardownChain extends LuceneTestCase {
+    @Override
+    public void tearDown() throws Exception {
+      // missing call.
+    }
+
+    @Test
+    public void testMe() {
+    }
+  }
+
+  public static class NestedAssumeInSetup extends LuceneTestCase {
+    @Override
+    public void setUp() throws Exception {
+      assumeTrue("foobar", false);
+    }
+
+    @Test
+    public void testMe() {
+    }
+  }
+
+  public static class ExceptionInTearDown extends LuceneTestCase {
+    @Override
+    public void tearDown() throws Exception {
+      super.tearDown();
+      throw new RuntimeException("foobar");
+    }
+
+    @Test
+    public void testMe() {
+    }
+  }
+
+  /**
+   * Verify super method calls on {@link LuceneTestCase#setUp()}.
+   */
+  @Test
+  public void testSetupChaining() {
+    Result result = JUnitCore.runClasses(NestedSetupChain.class);
+    Assert.assertEquals(1, result.getFailureCount());
+    Failure failure = result.getFailures().get(0);
+    Assert.assertTrue(failure.getMessage().contains("One of the overrides of setUp does not propagate the call."));
+  }
+  
+  /**
+   * Verify super method calls on {@link LuceneTestCase#tearDown()}.
+   */
+  @Test
+  public void testTeardownChaining() {
+    Result result = JUnitCore.runClasses(NestedTeardownChain.class);
+    Assert.assertEquals(1, result.getFailureCount());
+    Failure failure = result.getFailures().get(0);
+    Assert.assertTrue(failure.getMessage().contains("One of the overrides of tearDown does not propagate the call."));
+  }
+
+  /**
+   * Verify failures in {@link LuceneTestCase#tearDown()} are properly reported.
+   */
+  @Test
+  public void testExceptionInTearDown() {
+    Result result = JUnitCore.runClasses(ExceptionInTearDown.class);
+    Assert.assertEquals(1, result.getFailureCount());
+    Failure failure = result.getFailures().get(0);
+    Assert.assertTrue(failure.getMessage().contains("foobar"));
+  }
+
+  /**
+   * Tests that throwing {@link AssumptionViolatedException} at the 
+   * {@link LuceneTestCase#setUp()} results in a {@link Failure} being 
+   * propagated to {@link RunListener}s.
+   */
+  @Test
+  public void testMessageInSetupAssumption() {
+    JUnitCore core = new JUnitCore();
+    final List<Failure> failures = new ArrayList<Failure>();
+    core.addListener(new RunListener() {
+      @Override
+      public void testAssumptionFailure(Failure failure) {
+        failures.add(failure);
+      }
+      
+      @Override
+      public void testFailure(Failure failure) throws Exception {
+        failures.add(failure);
+      }
+    });
+    Result result = core.run(NestedAssumeInSetup.class);
+
+    Assert.assertEquals(0, result.getFailureCount());
+    Assert.assertEquals(0, result.getIgnoreCount());
+    Assert.assertEquals(1, result.getRunCount());
+    
+    // Note that result.getFailures() will return an empty list because by default
+    // JUnit's listener ignores assumption failures. We tracked them on our own though,
+    // so we have it.
+    Assert.assertEquals(1, failures.size());
+    Assert.assertTrue(failures.get(0).getMessage().contains("foobar"));
+  }    
+}
diff --git a/lucene/core/src/test/org/apache/lucene/util/fst/TestFSTs.java b/lucene/core/src/test/org/apache/lucene/util/fst/TestFSTs.java
index 9108a7a..02c7840 100644
--- a/lucene/core/src/test/org/apache/lucene/util/fst/TestFSTs.java
+++ b/lucene/core/src/test/org/apache/lucene/util/fst/TestFSTs.java
@@ -1383,7 +1383,7 @@ public class TestFSTs extends LuceneTestCase {
     }
   }
 
-  // java -cp build/classes/test:build/classes/test-framework:build/classes/java:lib/junit-4.7.jar org.apache.lucene.util.fst.TestFSTs /x/tmp/allTerms3.txt out
+  // java -cp build/classes/test:build/classes/test-framework:build/classes/java:lib/junit-4.10.jar org.apache.lucene.util.fst.TestFSTs /x/tmp/allTerms3.txt out
   public static void main(String[] args) throws IOException {
     int prune = 0;
     int limit = Integer.MAX_VALUE;
diff --git a/lucene/lib/junit-4.10.jar b/lucene/lib/junit-4.10.jar
new file mode 100644
index 0000000..954851e
Binary files /dev/null and b/lucene/lib/junit-4.10.jar differ
diff --git a/lucene/lib/junit-4.7.jar b/lucene/lib/junit-4.7.jar
deleted file mode 100755
index 700ad69..0000000
Binary files a/lucene/lib/junit-4.7.jar and /dev/null differ
diff --git a/lucene/lib/junit-NOTICE.txt b/lucene/lib/junit-NOTICE.txt
index ee48d04..f9796ea 100644
--- a/lucene/lib/junit-NOTICE.txt
+++ b/lucene/lib/junit-NOTICE.txt
@@ -1,2 +1,2 @@
-JUnit (under lib/junit-4.7.jar) is licensed under the Common Public License v. 1.0
+JUnit (under lib/junit-4.10.jar) is licensed under the Common Public License v. 1.0
 See http://junit.sourceforge.net/cpl-v10.html
\ No newline at end of file
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/InternalAssumptionViolatedException.java b/lucene/test-framework/src/java/org/apache/lucene/util/InternalAssumptionViolatedException.java
new file mode 100644
index 0000000..2bece4b
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/util/InternalAssumptionViolatedException.java
@@ -0,0 +1,57 @@
+package org.apache.lucene.util;
+
+/**
+ * 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.
+ */
+
+import org.hamcrest.Description;
+import org.junit.internal.AssumptionViolatedException;
+
+/**
+ * We have our own "custom" assumption class because JUnit's {@link AssumptionViolatedException}
+ * does not allow a cause exception to be set.
+ * 
+ * <p>We currently subclass and substitute JUnit's internal AVE.
+ */
+@SuppressWarnings("serial") 
+final class InternalAssumptionViolatedException extends AssumptionViolatedException {
+  private final String message;
+
+  public InternalAssumptionViolatedException(String message) {
+    this(message, null);
+  }
+
+  public InternalAssumptionViolatedException(String message, Throwable t) {
+    super(t, /* no matcher. */ null);
+    if (getCause() != t) {
+      throw new Error("AssumptionViolationException not setting up getCause() properly? Panic.");
+    }
+    this.message = message;
+  }
+
+  @Override
+  public String getMessage() {
+    return super.getMessage();
+  }
+
+  @Override
+  public void describeTo(Description description) {
+    description.appendText("failed assumption: " + message);
+    if (getCause() != null) {
+      description.appendText("(throwable: " + getCause().toString() + ")");
+    }
+  }
+}
\ No newline at end of file
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
index 9dda768..b7f2814 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/util/LuceneTestCase.java
@@ -26,8 +26,20 @@ import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+import java.util.TimeZone;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -41,12 +53,25 @@ import org.apache.lucene.codecs.lucene40.Lucene40Codec;
 import org.apache.lucene.codecs.simpletext.SimpleTextCodec;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldType;
-import org.apache.lucene.index.*;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexReader.ReaderClosedListener;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LogByteSizeMergePolicy;
+import org.apache.lucene.index.LogDocMergePolicy;
+import org.apache.lucene.index.LogMergePolicy;
+import org.apache.lucene.index.MockRandomMergePolicy;
+import org.apache.lucene.index.RandomCodec;
+import org.apache.lucene.index.RandomDocumentsWriterPerThreadPool;
+import org.apache.lucene.index.SegmentReader;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.index.SlowCompositeReaderWrapper;
+import org.apache.lucene.index.ThreadAffinityDocumentsWriterThreadPool;
+import org.apache.lucene.index.TieredMergePolicy;
+import org.apache.lucene.search.AssertingIndexSearcher;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.FieldCache;
 import org.apache.lucene.search.FieldCache.CacheEntry;
-import org.apache.lucene.search.AssertingIndexSearcher;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.RandomSimilarityProvider;
 import org.apache.lucene.search.similarities.DefaultSimilarity;
@@ -61,11 +86,18 @@ import org.apache.lucene.store.MockDirectoryWrapper;
 import org.apache.lucene.store.MockDirectoryWrapper.Throttling;
 import org.apache.lucene.store.NRTCachingDirectory;
 import org.apache.lucene.util.FieldCacheSanityChecker.Insanity;
-import org.junit.*;
-import org.junit.rules.MethodRule;
-import org.junit.rules.TestWatchman;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.FrameworkMethod;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.rules.*;
+import org.junit.runner.*;
+import org.junit.runner.notification.RunListener;
 import org.junit.runners.model.Statement;
 
 /**
@@ -97,7 +129,6 @@ import org.junit.runners.model.Statement;
 
 @RunWith(LuceneTestCaseRunner.class)
 public abstract class LuceneTestCase extends Assert {
-
   /**
    * true iff tests are run in verbose mode. Note: if it is false, tests are not
    * expected to print any messages.
@@ -172,20 +203,20 @@ public abstract class LuceneTestCase extends Assert {
   /** @lucene.internal */
   public static boolean PREFLEX_IMPERSONATION_IS_ACTIVE;
 
+  /**
+   * @see SubclassSetupTeardownRule  
+   */
+  private boolean setupCalled;
+
+  /**
+   * @see SubclassSetupTeardownRule
+   */
+  private boolean teardownCalled;
+
   private int savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount();
 
   private volatile Thread.UncaughtExceptionHandler savedUncaughtExceptionHandler = null;
 
-  /** Used to track if setUp and tearDown are called correctly from subclasses */
-  private static State state = State.INITIAL;
-
-  private static enum State {
-    INITIAL, // no tests ran yet
-    SETUP,   // test has called setUp()
-    RANTEST, // test is running
-    TEARDOWN // test has called tearDown()
-  }
-  
   /**
    * Some tests expect the directory to contain a single segment, and want to do tests on that segment's reader.
    * This is an utility method to help them.
@@ -240,7 +271,6 @@ public abstract class LuceneTestCase extends Assert {
   @BeforeClass
   public static void beforeClassLuceneTestCaseJ4() {
     initRandom();
-    state = State.INITIAL;
     tempDirs.clear();
     stores = Collections.synchronizedMap(new IdentityHashMap<MockDirectoryWrapper,StackTraceElement[]>());
     
@@ -337,18 +367,7 @@ public abstract class LuceneTestCase extends Assert {
 
   @AfterClass
   public static void afterClassLuceneTestCaseJ4() {
-    State oldState = state; // capture test execution state
-    state = State.INITIAL; // set the state for subsequent tests
-    
     Throwable problem = null;
-    try {
-      if (!testsFailed) {
-        assertTrue("ensure your setUp() calls super.setUp() and your tearDown() calls super.tearDown()!!!", 
-          oldState == State.INITIAL || oldState == State.TEARDOWN);
-      }
-    } catch (Throwable t) {
-      if (problem == null) problem = t;
-    }
     
     if (! "false".equals(TEST_CLEAN_THREADS)) {
       int rogueThreads = threadCleanup("test class");
@@ -469,77 +488,128 @@ public abstract class LuceneTestCase extends Assert {
 
   protected static boolean testsFailed; /* true if any tests failed */
 
-  // This is how we get control when errors occur.
-  // Think of this as start/end/success/failed
-  // events.
-  @Rule
-  public final TestWatchman intercept = new TestWatchman() {
-
+  /**
+   * Control the outcome of each test's output status (failure, assumption-failure). This
+   * would ideally be handled by attaching a {@link RunListener} to a {@link Runner} (because
+   * then we would be notified about static block failures).
+   */
+  private class TestResultInterceptorRule implements TestRule {
     @Override
-    public void failed(Throwable e, FrameworkMethod method) {
-      // org.junit.internal.AssumptionViolatedException in older releases
-      // org.junit.Assume.AssumptionViolatedException in recent ones
-      if (e.getClass().getName().endsWith("AssumptionViolatedException")) {
-        if (e.getCause() instanceof _TestIgnoredException)
-          e = e.getCause();
-        System.err.print("NOTE: Assume failed in '" + method.getName() + "' (ignored):");
-        if (VERBOSE) {
-          System.err.println();
-          e.printStackTrace(System.err);
-        } else {
-          System.err.print(" ");
-          System.err.println(e.getMessage());
+    public Statement apply(final Statement base, final Description description) {
+      return new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+          starting(description);
+          try {
+            base.evaluate();
+          } catch (AssumptionViolatedException e) {
+            assumptionIgnored(e, description);
+            throw e;
+          } catch (Throwable t) {
+            failed(t, description);
+            throw t;
+          } finally {
+            ending(description);
+          }
         }
+      };
+    }
+
+    private void assumptionIgnored(AssumptionViolatedException e, Description description) {
+      System.err.print("NOTE: Assume failed in '" + description.getDisplayName() + "' (ignored):");
+      if (VERBOSE) {
+        System.err.println();
+        e.printStackTrace(System.err);
       } else {
-        testsFailed = true;
-        reportAdditionalFailureInfo();
+        System.err.print(" ");
+        System.err.println(e.getMessage());
       }
-      super.failed(e, method);
     }
 
-    @Override
-    public void starting(FrameworkMethod method) {
+    private void failed(Throwable e, Description description) {
+      testsFailed = true;
+      reportAdditionalFailureInfo();
+      assert !(e instanceof AssumptionViolatedException);
+    }
+
+    private void starting(Description description) {
       // set current method name for logging
-      LuceneTestCase.this.name = method.getName();
-      State s = state; // capture test execution state
-      state = State.RANTEST; // set the state for subsequent tests
-      if (!testsFailed) {
-        assertTrue("ensure your setUp() calls super.setUp()!!!", s == State.SETUP);
-      }
-      super.starting(method);
+      LuceneTestCase.this.name = description.getDisplayName();
+    }
+
+    private void ending(Description description) {
+      // clear the current method name.
+      LuceneTestCase.this.name = null;
     }
   };
-  
+
   /** 
    * The thread executing the current test case.
    * @see #isTestThread()
    */
   volatile Thread testCaseThread;
 
-  /** @see #testCaseThread */
-  @Rule
-  public final MethodRule setTestThread = new MethodRule() {
-    public Statement apply(final Statement s, FrameworkMethod fm, Object target) {
+  /** 
+   * @see LuceneTestCase#testCaseThread 
+   */
+  private class RememberThreadRule implements TestRule {
+    @Override
+    public Statement apply(final Statement base, Description description) {
       return new Statement() {
         public void evaluate() throws Throwable {
           try {
             LuceneTestCase.this.testCaseThread = Thread.currentThread();
-            s.evaluate();
+            base.evaluate();
           } finally {
             LuceneTestCase.this.testCaseThread = null;
           }
         }
       };
     }
-  };
+  }
 
-  @Before
-  public void setUp() throws Exception {
+  /**
+   * This controls how rules are nested. It is important that _all_ rules declared
+   * in {@link LuceneTestCase} are executed in proper order if they depend on each 
+   * other.
+   */
+  @Rule
+  public final TestRule ruleChain = RuleChain
+    .outerRule(new RememberThreadRule())
+    .around(new TestResultInterceptorRule())
+    .around(new InternalSetupTeardownRule())
+    .around(new SubclassSetupTeardownRule());
+
+  /**
+   * Internal {@link LuceneTestCase} setup before/after each test.
+   */
+  private class InternalSetupTeardownRule implements TestRule {
+    @Override
+    public Statement apply(final Statement base, Description description) {
+      return new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+          setUpInternal();
+          // We simulate the previous behavior of @Before in that
+          // if any statement below us fails, we just propagate the original
+          // exception and do not call tearDownInternal.
+
+          // TODO: [DW] should this really be this way? We could use
+          // JUnit's MultipleFailureException and propagate both?
+          base.evaluate();
+          tearDownInternal();
+        }
+      };
+    }
+  }
+  
+  /**
+   * Setup before the tests.
+   */
+  private final void setUpInternal() throws Exception {
     seed = "random".equals(TEST_SEED) ? seedRand.nextLong() : ThreeLongs.fromString(TEST_SEED).l2;
     random.setSeed(seed);
-    State s = state; // capture test execution state
-    state = State.SETUP; // set the state for subsequent tests
-   
+    
     savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
     Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
       public void uncaughtException(Thread t, Throwable e) {
@@ -553,8 +623,6 @@ public abstract class LuceneTestCase extends Assert {
               break;
             }
           }
-          if (e.getCause() instanceof _TestIgnoredException)
-            e = e.getCause();
           System.err.print("NOTE: Assume failed at " + where + " (ignored):");
           if (VERBOSE) {
             System.err.println();
@@ -574,10 +642,6 @@ public abstract class LuceneTestCase extends Assert {
 
     savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount();
 
-    if (!testsFailed) {
-      assertTrue("ensure your tearDown() calls super.tearDown()!!!", (s == State.INITIAL || s == State.TEARDOWN));
-    }
-    
     if (useNoMemoryExpensiveCodec) {
       String defFormat = _TestUtil.getPostingsFormat("thisCodeMakesAbsolutelyNoSenseCanWeDeleteIt");
       // Stupid: assumeFalse in setUp() does not print any information, because
@@ -618,26 +682,57 @@ public abstract class LuceneTestCase extends Assert {
     return Thread.currentThread() == testCaseThread;
   }
 
+  /**
+   * Make sure {@link #setUp()} and {@link #tearDown()} were invoked even if they
+   * have been overriden. We assume nobody will call these out of non-overriden
+   * methods (they have to be public by contract, unfortunately). The top-level
+   * methods just set a flag that is checked upon successful execution of each test
+   * case.
+   */
+  private class SubclassSetupTeardownRule implements TestRule {
+    @Override
+    public Statement apply(final Statement base, Description description) {
+      return new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+          setupCalled = false;
+          teardownCalled = false;
+          base.evaluate();
+
+          // I assume we don't want to check teardown chaining if something happens in the
+          // test because this would obscure the original exception?
+          if (!setupCalled) { 
+            Assert.fail("One of the overrides of setUp does not propagate the call.");
+          }
+          if (!teardownCalled) { 
+            Assert.fail("One of the overrides of tearDown does not propagate the call.");
+          }
+        }
+      };
+    }
+  }
+
+  /**
+   * For subclassing only. Overrides must call {@code super.setUp()}.
+   */
+  @Before
+  public void setUp() throws Exception {
+    setupCalled = true;
+  }
+
+  /**
+   * For subclassing only. Overrides must call {@code super.tearDown()}.
+   */
   @After
   public void tearDown() throws Exception {
-    State oldState = state; // capture test execution state
-    state = State.TEARDOWN; // set the state for subsequent tests
-    
-    // NOTE: with junit 4.7, we don't get a reproduceWith because our Watchman
-    // does not know if something fails in tearDown. so we ensure this happens ourselves for now.
-    // we can remove this if we upgrade to 4.8
-    Throwable problem = null;
-    
-    try {
-      if (!testsFailed) {
-        // Note: we allow a test to go straight from SETUP -> TEARDOWN (without ever entering the RANTEST state)
-        // because if you assume() inside setUp(), it skips the test and the TestWatchman has no way to know...
-        assertTrue("ensure your setUp() calls super.setUp()!!!", oldState == State.RANTEST || oldState == State.SETUP);
-      }
-    } catch (Throwable t) {
-      if (problem == null) problem = t;
-    }
+    teardownCalled = true;
+  }
 
+  /**
+   * Clean up after tests.
+   */
+  private final void tearDownInternal() throws Exception {
+    Throwable problem = null;
     BooleanQuery.setMaxClauseCount(savedBoolMaxClauseCount);
 
     // this won't throw any exceptions or fail the test
@@ -673,8 +768,8 @@ public abstract class LuceneTestCase extends Assert {
     purgeFieldCache(FieldCache.DEFAULT);
     
     if (problem != null) {
-      testsFailed = true;
       reportAdditionalFailureInfo();
+      // TODO: simply rethrow problem, without wrapping?
       throw new RuntimeException(problem);
     }
   }
@@ -858,7 +953,7 @@ public abstract class LuceneTestCase extends Assert {
   }
 
   public static void assumeTrue(String msg, boolean b) {
-    Assume.assumeNoException(b ? null : new _TestIgnoredException(msg));
+    Assume.assumeNoException(b ? null : new InternalAssumptionViolatedException(msg));
   }
 
   public static void assumeFalse(String msg, boolean b) {
@@ -866,7 +961,7 @@ public abstract class LuceneTestCase extends Assert {
   }
 
   public static void assumeNoException(String msg, Exception e) {
-    Assume.assumeNoException(e == null ? null : new _TestIgnoredException(msg, e));
+    Assume.assumeNoException(e == null ? null : new InternalAssumptionViolatedException(msg, e));
   }
 
   public static <T> Set<T> asSet(T... args) {
diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/_TestIgnoredException.java b/lucene/test-framework/src/java/org/apache/lucene/util/_TestIgnoredException.java
deleted file mode 100644
index 3664cb0..0000000
--- a/lucene/test-framework/src/java/org/apache/lucene/util/_TestIgnoredException.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.lucene.util;
-
-/**
- * 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.
- */
-
-import java.io.PrintStream;
-
-/** Replacement for Assume jUnit class, so we can add a message with explanation */
-final class _TestIgnoredException extends RuntimeException {
-  
-  _TestIgnoredException(String msg) {
-    super(msg);
-  }
-  
-  _TestIgnoredException(String msg, Throwable t) {
-    super(msg, t);
-  }
-  
-  @Override
-  public String getMessage() {
-    StringBuilder sb = new StringBuilder(super.getMessage());
-    if (getCause() != null)
-      sb.append(" - ").append(getCause());
-    return sb.toString();
-  }
-  
-  // only this one is called by our code, exception is not used outside this class:
-  @Override
-  public void printStackTrace(PrintStream s) {
-    if (getCause() != null) {
-      s.println(super.toString() + " - Caused by:");
-      getCause().printStackTrace(s);
-    } else {
-      super.printStackTrace(s);
-    }
-  }
-}
diff --git a/solr/NOTICE.txt b/solr/NOTICE.txt
index 140a115..6e18318 100644
--- a/solr/NOTICE.txt
+++ b/solr/NOTICE.txt
@@ -75,7 +75,7 @@ the Apache CXF project and is Apache License 2.0.
 The Google Code Prettify is Apache License 2.0.
 See http://code.google.com/p/google-code-prettify/
 
-JUnit (under lib/junit-4.7.jar) is licensed under the Common Public License v. 1.0
+JUnit (under lib/junit-4.10.jar) is licensed under the Common Public License v. 1.0
 See http://junit.sourceforge.net/cpl-v10.html
 
 JLine (under contrib/lucli/lib/jline.jar) is licensed under the BSD License.
diff --git a/solr/contrib/dataimporthandler-extras/src/test-files/dihextras/solr/conf/dataimport.properties b/solr/contrib/dataimporthandler-extras/src/test-files/dihextras/solr/conf/dataimport.properties
new file mode 100644
index 0000000..55cb855
--- /dev/null
+++ b/solr/contrib/dataimporthandler-extras/src/test-files/dihextras/solr/conf/dataimport.properties
@@ -0,0 +1,4 @@
+#Wed Feb 08 14:04:06 CVT 2012
+18623746288561.last_index_time=2012-02-08 14\:04\:05
+18624825699733.last_index_time=2012-02-08 14\:04\:06
+last_index_time=2012-02-08 14\:04\:06
diff --git a/solr/contrib/dataimporthandler/src/test-files/dih/solr/conf/dataimport.properties b/solr/contrib/dataimporthandler/src/test-files/dih/solr/conf/dataimport.properties
new file mode 100644
index 0000000..98f1be5
--- /dev/null
+++ b/solr/contrib/dataimporthandler/src/test-files/dih/solr/conf/dataimport.properties
@@ -0,0 +1,4 @@
+#Wed Feb 08 23:04:23 CST 2012
+x.last_index_time=2012-02-08 12\:04\:23
+parent.last_index_time=2012-02-08 12\:04\:23
+last_index_time=2012-02-08 12\:04\:23
diff --git a/solr/lib/junit-4.10.jar b/solr/lib/junit-4.10.jar
new file mode 100644
index 0000000..954851e
Binary files /dev/null and b/solr/lib/junit-4.10.jar differ
diff --git a/solr/lib/junit-4.7.jar b/solr/lib/junit-4.7.jar
deleted file mode 100755
index 700ad69..0000000
Binary files a/solr/lib/junit-4.7.jar and /dev/null differ
