mirror of https://github.com/apache/lucene.git
LUCENE-8999: LuceneTestCase.expectThrows now propogates assert/assumption failures up to the test w/o wrapping in a new assertion failure unless the caller has explicitly expected them
This commit is contained in:
parent
ceb47042d1
commit
4d0afd4aff
|
@ -189,6 +189,9 @@ Other
|
|||
|
||||
* LUCENE-8998: Fix OverviewImplTest.testIsOptimized reproducible failure. (Tomoko Uchida)
|
||||
|
||||
* LUCENE-8999: LuceneTestCase.expectThrows now propogates assert/assumption failures up to the test
|
||||
w/o wrapping in a new assertion failure unless the caller has explicitly expected them (hossman)
|
||||
|
||||
======================= Lucene 8.2.0 =======================
|
||||
|
||||
API Changes
|
||||
|
|
|
@ -115,6 +115,7 @@ import org.junit.Test;
|
|||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.JUnit4MethodProvider;
|
||||
import com.carrotsearch.randomizedtesting.LifecycleScope;
|
||||
|
@ -2720,17 +2721,16 @@ public abstract class LuceneTestCase extends Assert {
|
|||
|
||||
/** Checks a specific exception class is thrown by the given runnable, and returns it. */
|
||||
public static <T extends Throwable> T expectThrows(Class<T> expectedType, String noExceptionMessage, ThrowingRunnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
if (expectedType.isInstance(e)) {
|
||||
return expectedType.cast(e);
|
||||
}
|
||||
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected " + expectedType.getSimpleName() + " but got " + e);
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
final Throwable thrown = _expectThrows(Collections.singletonList(expectedType), runnable);
|
||||
if (expectedType.isInstance(thrown)) {
|
||||
return expectedType.cast(thrown);
|
||||
}
|
||||
throw new AssertionFailedError(noExceptionMessage);
|
||||
if (null == thrown) {
|
||||
throw new AssertionFailedError(noExceptionMessage);
|
||||
}
|
||||
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected " + expectedType.getSimpleName() + " but got " + thrown);
|
||||
assertion.initCause(thrown);
|
||||
throw assertion;
|
||||
}
|
||||
|
||||
/** Checks a specific exception class is thrown by the given runnable, and returns it. */
|
||||
|
@ -2739,16 +2739,13 @@ public abstract class LuceneTestCase extends Assert {
|
|||
throw new AssertionError("At least one expected exception type is required?");
|
||||
}
|
||||
|
||||
Throwable thrown = null;
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
final Throwable thrown = _expectThrows(expectedTypes, runnable);
|
||||
if (null != thrown) {
|
||||
for (Class<? extends T> expectedType : expectedTypes) {
|
||||
if (expectedType.isInstance(e)) {
|
||||
return expectedType.cast(e);
|
||||
if (expectedType.isInstance(thrown)) {
|
||||
return expectedType.cast(thrown);
|
||||
}
|
||||
}
|
||||
thrown = e;
|
||||
}
|
||||
|
||||
List<String> exceptionTypes = expectedTypes.stream().map(c -> c.getSimpleName()).collect(Collectors.toList());
|
||||
|
@ -2771,29 +2768,28 @@ public abstract class LuceneTestCase extends Assert {
|
|||
*/
|
||||
public static <TO extends Throwable, TW extends Throwable> TW expectThrows
|
||||
(Class<TO> expectedOuterType, Class<TW> expectedWrappedType, ThrowingRunnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
if (expectedOuterType.isInstance(e)) {
|
||||
Throwable cause = e.getCause();
|
||||
if (expectedWrappedType.isInstance(cause)) {
|
||||
return expectedWrappedType.cast(cause);
|
||||
} else {
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected wrapped exception type, expected " + expectedWrappedType.getSimpleName()
|
||||
+ " but got: " + cause);
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
}
|
||||
}
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected outer exception type, expected " + expectedOuterType.getSimpleName()
|
||||
+ " but got: " + e);
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
final Throwable thrown = _expectThrows(Collections.singletonList(expectedOuterType), runnable);
|
||||
if (null == thrown) {
|
||||
throw new AssertionFailedError("Expected outer exception " + expectedOuterType.getSimpleName()
|
||||
+ " but no exception was thrown.");
|
||||
}
|
||||
throw new AssertionFailedError("Expected outer exception " + expectedOuterType.getSimpleName()
|
||||
+ " but no exception was thrown.");
|
||||
if (expectedOuterType.isInstance(thrown)) {
|
||||
Throwable cause = thrown.getCause();
|
||||
if (expectedWrappedType.isInstance(cause)) {
|
||||
return expectedWrappedType.cast(cause);
|
||||
} else {
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected wrapped exception type, expected " + expectedWrappedType.getSimpleName()
|
||||
+ " but got: " + cause);
|
||||
assertion.initCause(thrown);
|
||||
throw assertion;
|
||||
}
|
||||
}
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected outer exception type, expected " + expectedOuterType.getSimpleName()
|
||||
+ " but got: " + thrown);
|
||||
assertion.initCause(thrown);
|
||||
throw assertion;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2805,41 +2801,65 @@ public abstract class LuceneTestCase extends Assert {
|
|||
*/
|
||||
public static <TO extends Throwable, TW extends Throwable> TO expectThrowsAnyOf
|
||||
(LinkedHashMap<Class<? extends TO>,List<Class<? extends TW>>> expectedOuterToWrappedTypes, ThrowingRunnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
for (Map.Entry<Class<? extends TO>, List<Class<? extends TW>>> entry : expectedOuterToWrappedTypes.entrySet()) {
|
||||
Class<? extends TO> expectedOuterType = entry.getKey();
|
||||
List<Class<? extends TW>> expectedWrappedTypes = entry.getValue();
|
||||
Throwable cause = e.getCause();
|
||||
if (expectedOuterType.isInstance(e)) {
|
||||
if (expectedWrappedTypes.isEmpty()) {
|
||||
return null; // no wrapped exception
|
||||
} else {
|
||||
for (Class<? extends TW> expectedWrappedType : expectedWrappedTypes) {
|
||||
if (expectedWrappedType.isInstance(cause)) {
|
||||
return expectedOuterType.cast(e);
|
||||
}
|
||||
final List<Class<? extends TO>> outerClasses = expectedOuterToWrappedTypes.keySet().stream().collect(Collectors.toList());
|
||||
final Throwable thrown = _expectThrows(outerClasses, runnable);
|
||||
|
||||
if (null == thrown) {
|
||||
List<String> outerTypes = outerClasses.stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
throw new AssertionFailedError("Expected any of the following outer exception types: " + outerTypes
|
||||
+ " but no exception was thrown.");
|
||||
}
|
||||
for (Map.Entry<Class<? extends TO>, List<Class<? extends TW>>> entry : expectedOuterToWrappedTypes.entrySet()) {
|
||||
Class<? extends TO> expectedOuterType = entry.getKey();
|
||||
List<Class<? extends TW>> expectedWrappedTypes = entry.getValue();
|
||||
Throwable cause = thrown.getCause();
|
||||
if (expectedOuterType.isInstance(thrown)) {
|
||||
if (expectedWrappedTypes.isEmpty()) {
|
||||
return null; // no wrapped exception
|
||||
} else {
|
||||
for (Class<? extends TW> expectedWrappedType : expectedWrappedTypes) {
|
||||
if (expectedWrappedType.isInstance(cause)) {
|
||||
return expectedOuterType.cast(thrown);
|
||||
}
|
||||
List<String> wrappedTypes = expectedWrappedTypes.stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected wrapped exception type, expected one of " + wrappedTypes + " but got: " + cause);
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
}
|
||||
List<String> wrappedTypes = expectedWrappedTypes.stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected wrapped exception type, expected one of " + wrappedTypes + " but got: " + cause);
|
||||
assertion.initCause(thrown);
|
||||
throw assertion;
|
||||
}
|
||||
}
|
||||
List<String> outerTypes = expectedOuterToWrappedTypes.keySet().stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected outer exception type, expected one of " + outerTypes + " but got: " + e);
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
}
|
||||
List<String> outerTypes = expectedOuterToWrappedTypes.keySet().stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
throw new AssertionFailedError("Expected any of the following outer exception types: " + outerTypes
|
||||
+ " but no exception was thrown.");
|
||||
List<String> outerTypes = outerClasses.stream().map(Class::getSimpleName).collect(Collectors.toList());
|
||||
AssertionFailedError assertion = new AssertionFailedError
|
||||
("Unexpected outer exception type, expected one of " + outerTypes + " but got: " + thrown);
|
||||
assertion.initCause(thrown);
|
||||
throw assertion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@link #expectThrows} and {@link #expectThrowsAnyOf} that takes care of propagating
|
||||
* any {@link AssertionError} or {@link AssumptionViolatedException} instances thrown if and only if they
|
||||
* are super classes of the <code>expectedTypes</code>. Otherwise simply returns any {@link Throwable}
|
||||
* thrown, regardless of type, or null if the <code>runnable</code> completed w/o error.
|
||||
*/
|
||||
private static Throwable _expectThrows(List<? extends Class<?>> expectedTypes, ThrowingRunnable runnable) {
|
||||
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (AssertionError | AssumptionViolatedException ae) {
|
||||
for (Class<?> expectedType : expectedTypes) {
|
||||
if (expectedType.isInstance(ae)) { // user is expecting this type explicitly
|
||||
return ae;
|
||||
}
|
||||
}
|
||||
throw ae;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns true if the file exists (can be opened), false
|
||||
* if it cannot be opened, and (unlike Java's
|
||||
* File.exists) throws IOException if there's some
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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.lucene.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.internal.AssumptionViolatedException;
|
||||
|
||||
public class TestExpectThrows extends LuceneTestCase {
|
||||
|
||||
private static class HuperDuperException extends IOException {
|
||||
public HuperDuperException() {
|
||||
/* No-Op */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable throws (an
|
||||
* instance of a subclass of) the expected Exception type: by returning that Exception.
|
||||
*/
|
||||
public void testPass() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
final IOException returned = expectThrows(IOException.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
throw new HuperDuperException();
|
||||
});
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(returned);
|
||||
assertEquals(HuperDuperException.class, returned.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable does not throw (an
|
||||
* instance of a subclass of) the expected Exception type: by throwing an assertion to
|
||||
* <code>FAIL</code> the test.
|
||||
*/
|
||||
public void testFail() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
AssertionError caught = null;
|
||||
try {
|
||||
final IOException returned = expectThrows(IOException.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
});
|
||||
fail("must not complete"); // NOTE: we don't use expectThrows to test expectThrows
|
||||
} catch (AssertionError ae) {
|
||||
caught = ae;
|
||||
}
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(caught);
|
||||
assertEquals("Expected exception IOException but no exception was thrown", caught.getMessage());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable contains an
|
||||
* assertion that does not pass: by allowing that assertion to propogate and
|
||||
* <code>FAIL</code> the test.
|
||||
*/
|
||||
public void testNestedFail() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
AssertionError caught = null;
|
||||
try {
|
||||
final IOException returned = expectThrows(IOException.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
fail("this failure should propogate");
|
||||
});
|
||||
fail("must not complete"); // NOTE: we don't use expectThrows to test expectThrows
|
||||
} catch (AssertionError ae) {
|
||||
caught = ae;
|
||||
}
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(caught);
|
||||
assertEquals("this failure should propogate", caught.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable contains an
|
||||
* assumption that does not pass: by allowing that assumption to propogate and cause
|
||||
* the test to <code>SKIP</code>.
|
||||
*/
|
||||
public void testNestedAssume() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
AssumptionViolatedException caught = null;
|
||||
try {
|
||||
final IOException returned = expectThrows(IOException.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
assumeTrue("this assumption should propogate", false);
|
||||
});
|
||||
fail("must not complete"); // NOTE: we don't use expectThrows to test expectThrows
|
||||
} catch (AssumptionViolatedException ave) {
|
||||
caught = ave;
|
||||
}
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(caught);
|
||||
assertEquals("this assumption should propogate", caught.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable contains an
|
||||
* assertion that does not pass but the caller has explicitly said they expect an Exception of that type:
|
||||
* by returning that assertion failure Exception.
|
||||
*/
|
||||
public void testExpectingNestedFail() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
AssertionError returned = null;
|
||||
try {
|
||||
returned = expectThrows(AssertionError.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
fail("this failure should be returned, not propogated");
|
||||
});
|
||||
} catch (AssertionError caught) { // NOTE: we don't use expectThrows to test expectThrows
|
||||
assertNull("An exception should not have been thrown", caught);
|
||||
}
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(returned);
|
||||
assertEquals("this failure should be returned, not propogated", returned.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that {@link #expectThrows} behaves correctly when the Runnable contains an
|
||||
* assumption that does not pass but the caller has explicitly said they expect an Exception of that type:
|
||||
* by returning that assumption failure Exception.
|
||||
*/
|
||||
public void testExpectingNestedAssume() {
|
||||
final AtomicBoolean ran = new AtomicBoolean(false);
|
||||
AssumptionViolatedException returned = null;
|
||||
try {
|
||||
returned = expectThrows(AssumptionViolatedException.class, () -> {
|
||||
ran.getAndSet(true);
|
||||
assumeTrue("this assumption should be returned, not propogated", false);
|
||||
});
|
||||
} catch (AssumptionViolatedException caught) { // NOTE: we don't use expectThrows to test expectThrows
|
||||
assertNull("An exception should not have been thrown", caught);
|
||||
}
|
||||
assertTrue(ran.get());
|
||||
assertNotNull(returned);
|
||||
assertEquals("this assumption should be returned, not propogated", returned.getMessage());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue