diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 66082532d37..6bae9972bdf 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -24,6 +24,13 @@ Bug Fixes Build +* LUCENE-4160: Added a property to quit the tests after a given + number of failures has occurred. This is useful in combination + with -Dtests.iters=N (you can start N iterations and wait for M + failures, in particular M = 1). -Dtests.maxfailures=M. Alternatively, + specify -Dtests.failfast=true to skip all tests after the first failure. + (Dawid Weiss) + * LUCENE-4115: JAR resolution/ cleanup should be done automatically for ant clean/ eclipse/ resolve (Dawid Weiss) diff --git a/lucene/common-build.xml b/lucene/common-build.xml index 62914a2815b..c77076c973e 100644 --- a/lucene/common-build.xml +++ b/lucene/common-build.xml @@ -818,6 +818,12 @@ + + + + + + @@ -961,6 +967,10 @@ ant test -Dtests.iters=N -Dtestcase=ClassName -Dtests.seed=dead:beef ant test -Dtests.iters=N -Dtestcase=ClassName -Dtestmethod=mytest ant test -Dtests.iters=N -Dtestcase=ClassName -Dtests.method=mytest* +# Repeats N times but skips any tests after the first failure or M +# initial failures. +ant test -Dtests.iters=N -Dtests.failfast=yes -Dtestcase=... +ant test -Dtests.iters=N -Dtests.maxfailures=M -Dtestcase=... # # Test groups. ---------------------------------------------------- diff --git a/lucene/core/src/test/org/apache/lucene/util/TestMaxFailuresRule.java b/lucene/core/src/test/org/apache/lucene/util/TestMaxFailuresRule.java new file mode 100644 index 00000000000..67e8a55aede --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/util/TestMaxFailuresRule.java @@ -0,0 +1,72 @@ +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.apache.lucene.util.junitcompat.WithNestedTests; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.*; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +import com.carrotsearch.randomizedtesting.annotations.Repeat; +import com.carrotsearch.randomizedtesting.rules.SystemPropertiesInvariantRule; + +/** + * @see TestRuleIgnoreAfterMaxFailures + * @see SystemPropertiesInvariantRule + */ +public class TestMaxFailuresRule extends WithNestedTests { + public TestMaxFailuresRule() { + super(true); + } + + public static class Nested extends WithNestedTests.AbstractNestedTest { + @Repeat(iterations = 100) + public void testFailSometimes() { + assertFalse(random().nextInt(5) == 0); + } + } + + @Test + public void testMaxFailures() { + int maxFailures = LuceneTestCase.ignoreAfterMaxFailures.getMaxFailures(); + try { + LuceneTestCase.ignoreAfterMaxFailures.setMaxFailures(2); + + JUnitCore core = new JUnitCore(); + final int [] assumptions = new int [1]; + core.addListener(new RunListener() { + @Override + public void testAssumptionFailure(Failure failure) { + assumptions[0]++; + } + }); + + Result result = core.run(Nested.class); + Assert.assertEquals(2, result.getFailureCount()); + Assert.assertEquals(0, result.getIgnoreCount()); + Assert.assertEquals(100, result.getRunCount()); + // JUnit doesn't pass back the number of successful tests, just make sure + // we did have enough assumption-failures. + Assert.assertTrue(assumptions[0] > 50); + } finally { + LuceneTestCase.ignoreAfterMaxFailures.setMaxFailures(maxFailures); + } + } +} 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 14ad8edd6cc..95811668693 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 @@ -23,6 +23,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.*; +import java.util.logging.Logger; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Field.Store; @@ -115,15 +116,21 @@ import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAs @ThreadLeaks(failTestIfLeaking = false) public abstract class LuceneTestCase extends Assert { - // ----------------------------------------------------------------- - // Test groups and other annotations modifying tests' behavior. - // ----------------------------------------------------------------- - + // -------------------------------------------------------------------- + // Test groups, system properties and other annotations modifying tests + // -------------------------------------------------------------------- + public static final String SYSPROP_NIGHTLY = "tests.nightly"; public static final String SYSPROP_WEEKLY = "tests.weekly"; public static final String SYSPROP_AWAITSFIX = "tests.awaitsfix"; public static final String SYSPROP_SLOW = "tests.slow"; + /** @see #ignoreAfterMaxFailures*/ + private static final String SYSPROP_MAXFAILURES = "tests.maxfailures"; + + /** @see #ignoreAfterMaxFailures*/ + private static final String SYSPROP_FAILFAST = "tests.failfast"; + /** * Annotation for tests that should only be run during nightly builds. */ @@ -232,7 +239,7 @@ public abstract class LuceneTestCase extends Assert { /** Whether or not {@link Slow} tests should run. */ public static final boolean TEST_SLOW = systemPropertyAsBoolean(SYSPROP_SLOW, false); - + /** Throttling, see {@link MockDirectoryWrapper#setThrottling(Throttling)}. */ public static final Throttling TEST_THROTTLING = TEST_NIGHTLY ? Throttling.SOMETIMES : Throttling.NEVER; @@ -298,8 +305,30 @@ public abstract class LuceneTestCase extends Assert { /** * Suite failure marker (any error in the test or suite scope). */ - public static TestRuleMarkFailure suiteFailureMarker; - + public final static TestRuleMarkFailure suiteFailureMarker = + new TestRuleMarkFailure(); + + /** + * Ignore tests after hitting a designated number of initial failures. + */ + final static TestRuleIgnoreAfterMaxFailures ignoreAfterMaxFailures; + static { + int maxFailures = systemPropertyAsInt(SYSPROP_MAXFAILURES, Integer.MAX_VALUE); + boolean failFast = systemPropertyAsBoolean(SYSPROP_FAILFAST, false); + + if (failFast) { + if (maxFailures == Integer.MAX_VALUE) { + maxFailures = 1; + } else { + Logger.getLogger(LuceneTestCase.class.getSimpleName()).warning( + "Property '" + SYSPROP_MAXFAILURES + "'=" + maxFailures + ", 'failfast' is" + + " ignored."); + } + } + + ignoreAfterMaxFailures = new TestRuleIgnoreAfterMaxFailures(maxFailures); + } + /** * This controls how suite-level rules are nested. It is important that _all_ rules declared * in {@link LuceneTestCase} are executed in proper order if they depend on each @@ -308,7 +337,8 @@ public abstract class LuceneTestCase extends Assert { @ClassRule public static TestRule classRules = RuleChain .outerRule(new TestRuleIgnoreTestSuites()) - .around(suiteFailureMarker = new TestRuleMarkFailure()) + .around(ignoreAfterMaxFailures) + .around(suiteFailureMarker) .around(new TestRuleAssertionsRequired()) .around(new TestRuleNoStaticHooksShadowing()) .around(new TestRuleNoInstanceHooksOverrides()) @@ -329,7 +359,7 @@ public abstract class LuceneTestCase extends Assert { /** Save test thread and name. */ private TestRuleThreadAndTestName threadAndTestNameRule = new TestRuleThreadAndTestName(); - /** Taint test failures. */ + /** Taint suite result with individual test failures. */ private TestRuleMarkFailure testFailureMarker = new TestRuleMarkFailure(suiteFailureMarker); /** @@ -340,6 +370,7 @@ public abstract class LuceneTestCase extends Assert { @Rule public final TestRule ruleChain = RuleChain .outerRule(testFailureMarker) + .around(ignoreAfterMaxFailures) .around(threadAndTestNameRule) .around(new TestRuleReportUncaughtExceptions()) .around(new SystemPropertiesInvariantRule(IGNORED_INVARIANT_PROPERTIES)) diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreAfterMaxFailures.java b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreAfterMaxFailures.java new file mode 100644 index 00000000000..aca4177d534 --- /dev/null +++ b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreAfterMaxFailures.java @@ -0,0 +1,90 @@ +package org.apache.lucene.util; + +import org.junit.Assert; +import org.junit.internal.AssumptionViolatedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import com.carrotsearch.randomizedtesting.RandomizedTest; +import com.carrotsearch.randomizedtesting.annotations.Repeat; + +/* + * 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. + */ + +/** + * This rule keeps a count of failed tests (suites) and will result in an + * {@link AssumptionViolatedException} after a given number of failures for all + * tests following this condition. + * + *

+ * Aborting quickly on failed tests can be useful when used in combination with + * test repeats (via the {@link Repeat} annotation or system property). + */ +public final class TestRuleIgnoreAfterMaxFailures implements TestRule { + /** + * Maximum failures. + */ + private int maxFailures; + + /** + * Current count of failures. + */ + private int failuresSoFar; + + /** + * @param maxFailures + * The number of failures after which all tests are ignored. Must be + * greater or equal 1. + */ + public TestRuleIgnoreAfterMaxFailures(int maxFailures) { + Assert.assertTrue("maxFailures must be >= 1: " + maxFailures, maxFailures >= 1); + this.maxFailures = maxFailures; + } + + @Override + public Statement apply(final Statement s, final Description d) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (failuresSoFar >= maxFailures) { + RandomizedTest.assumeTrue("Ignored, failures limit reached (" + + failuresSoFar + " >= " + maxFailures + ").", false); + } + + try { + s.evaluate(); + } catch (Throwable t) { + if (!TestRuleMarkFailure.isAssumption(t)) { + System.out.println("#" + d); + failuresSoFar++; + } + throw t; + } + } + }; + } + + /** For tests only. */ + void setMaxFailures(int maxFailures) { + this.maxFailures = maxFailures; + } + + int getMaxFailures() { + return maxFailures; + } +} diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreTestSuites.java b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreTestSuites.java index 22a8e3f8559..6020904390a 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreTestSuites.java +++ b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleIgnoreTestSuites.java @@ -1,6 +1,5 @@ package org.apache.lucene.util; -import org.junit.Assume; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; diff --git a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleMarkFailure.java b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleMarkFailure.java index d40044599f4..be9c625c711 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleMarkFailure.java +++ b/lucene/test-framework/src/java/org/apache/lucene/util/TestRuleMarkFailure.java @@ -47,11 +47,8 @@ public final class TestRuleMarkFailure implements TestRule { try { s.evaluate(); } catch (Throwable t) { - for (Throwable t2 : expandFromMultiple(t)) { - if (!(t2 instanceof AssumptionViolatedException)) { - markFailed(); - break; - } + if (!isAssumption(t)) { + markFailed(); } throw t; } @@ -59,6 +56,19 @@ public final class TestRuleMarkFailure implements TestRule { }; } + /** + * Is a given exception (or a MultipleFailureException) an + * {@link AssumptionViolatedException}? + */ + public static boolean isAssumption(Throwable t) { + for (Throwable t2 : expandFromMultiple(t)) { + if (!(t2 instanceof AssumptionViolatedException)) { + return false; + } + } + return true; + } + /** * Expand from multi-exception wrappers. */