diff options
Diffstat (limited to 'src/main/java/org/junit/runners/model')
9 files changed, 222 insertions, 24 deletions
diff --git a/src/main/java/org/junit/runners/model/FrameworkField.java b/src/main/java/org/junit/runners/model/FrameworkField.java index 945e389..ea2b16f 100644 --- a/src/main/java/org/junit/runners/model/FrameworkField.java +++ b/src/main/java/org/junit/runners/model/FrameworkField.java @@ -14,12 +14,26 @@ import org.junit.runners.BlockJUnit4ClassRunner; public class FrameworkField extends FrameworkMember<FrameworkField> { private final Field field; - FrameworkField(Field field) { + /** + * Returns a new {@code FrameworkField} for {@code field}. + * + * <p>Access relaxed to {@code public} since version 4.13.1. + */ + public FrameworkField(Field field) { if (field == null) { throw new NullPointerException( "FrameworkField cannot be created without an underlying field."); } this.field = field; + + if (isPublic()) { + // This field could be a public field in a package-scope base class + try { + field.setAccessible(true); + } catch (SecurityException e) { + // We may get an IllegalAccessException when we try to access the field + } + } } @Override @@ -41,6 +55,11 @@ public class FrameworkField extends FrameworkMember<FrameworkField> { } @Override + boolean isBridgeMethod() { + return false; + } + + @Override protected int getModifiers() { return field.getModifiers(); } diff --git a/src/main/java/org/junit/runners/model/FrameworkMember.java b/src/main/java/org/junit/runners/model/FrameworkMember.java index 724f096..5634b3f 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMember.java +++ b/src/main/java/org/junit/runners/model/FrameworkMember.java @@ -12,15 +12,29 @@ public abstract class FrameworkMember<T extends FrameworkMember<T>> implements Annotatable { abstract boolean isShadowedBy(T otherMember); - boolean isShadowedBy(List<T> members) { - for (T each : members) { - if (isShadowedBy(each)) { - return true; + T handlePossibleBridgeMethod(List<T> members) { + for (int i = members.size() - 1; i >=0; i--) { + T otherMember = members.get(i); + if (isShadowedBy(otherMember)) { + if (otherMember.isBridgeMethod()) { + /* + * We need to return the previously-encountered bridge method + * because JUnit won't be able to call the parent method, + * because the parent class isn't public. + */ + members.remove(i); + return otherMember; + } + // We found a shadowed member that isn't a bridge method. Ignore it. + return null; } } - return false; + // No shadow or bridge method found. The caller should add *this* member. + return (T) this; } + abstract boolean isBridgeMethod(); + protected abstract int getModifiers(); /** diff --git a/src/main/java/org/junit/runners/model/FrameworkMethod.java b/src/main/java/org/junit/runners/model/FrameworkMethod.java index 3580052..4471407 100644 --- a/src/main/java/org/junit/runners/model/FrameworkMethod.java +++ b/src/main/java/org/junit/runners/model/FrameworkMethod.java @@ -28,6 +28,15 @@ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { "FrameworkMethod cannot be created without an underlying method."); } this.method = method; + + if (isPublic()) { + // This method could be a public method in a package-scope base class + try { + method.setAccessible(true); + } catch (SecurityException e) { + // We may get an IllegalAccessException when we try to call the method + } + } } /** @@ -149,6 +158,11 @@ public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { } @Override + boolean isBridgeMethod() { + return method.isBridge(); + } + + @Override public boolean equals(Object obj) { if (!FrameworkMethod.class.isInstance(obj)) { return false; diff --git a/src/main/java/org/junit/runners/model/InitializationError.java b/src/main/java/org/junit/runners/model/InitializationError.java index 841b565..dd9c8b3 100644 --- a/src/main/java/org/junit/runners/model/InitializationError.java +++ b/src/main/java/org/junit/runners/model/InitializationError.java @@ -14,7 +14,7 @@ public class InitializationError extends Exception { /* * We have to use the f prefix until the next major release to ensure * serialization compatibility. - * See https://github.com/junit-team/junit/issues/976 + * See https://github.com/junit-team/junit4/issues/976 */ private final List<Throwable> fErrors; diff --git a/src/main/java/org/junit/runners/model/InvalidTestClassError.java b/src/main/java/org/junit/runners/model/InvalidTestClassError.java new file mode 100644 index 0000000..57be610 --- /dev/null +++ b/src/main/java/org/junit/runners/model/InvalidTestClassError.java @@ -0,0 +1,39 @@ +package org.junit.runners.model; + +import java.util.List; + +/** + * Thrown by {@link org.junit.runner.Runner}s in case the class under test is not valid. + * <p> + * Its message conveniently lists all of the validation errors. + * + * @since 4.13 + */ +public class InvalidTestClassError extends InitializationError { + private static final long serialVersionUID = 1L; + + private final String message; + + public InvalidTestClassError(Class<?> offendingTestClass, List<Throwable> validationErrors) { + super(validationErrors); + this.message = createMessage(offendingTestClass, validationErrors); + } + + private static String createMessage(Class<?> testClass, List<Throwable> validationErrors) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("Invalid test class '%s':", testClass.getName())); + int i = 1; + for (Throwable error : validationErrors) { + sb.append("\n " + (i++) + ". " + error.getMessage()); + } + return sb.toString(); + } + + /** + * @return a message with a list of all of the validation errors + */ + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/junit/runners/model/MemberValueConsumer.java b/src/main/java/org/junit/runners/model/MemberValueConsumer.java new file mode 100644 index 0000000..a6157bf --- /dev/null +++ b/src/main/java/org/junit/runners/model/MemberValueConsumer.java @@ -0,0 +1,18 @@ +package org.junit.runners.model; + +/** + * Represents a receiver for values of annotated fields/methods together with the declaring member. + * + * @see TestClass#collectAnnotatedFieldValues(Object, Class, Class, MemberValueConsumer) + * @see TestClass#collectAnnotatedMethodValues(Object, Class, Class, MemberValueConsumer) + * @since 4.13 + */ +public interface MemberValueConsumer<T> { + /** + * Receives the next value and its declaring member. + * + * @param member declaring member ({@link FrameworkMethod} or {@link FrameworkField}) + * @param value the value of the next member + */ + void accept(FrameworkMember<?> member, T value); +} diff --git a/src/main/java/org/junit/runners/model/MultipleFailureException.java b/src/main/java/org/junit/runners/model/MultipleFailureException.java index 325c645..8e355a7 100644 --- a/src/main/java/org/junit/runners/model/MultipleFailureException.java +++ b/src/main/java/org/junit/runners/model/MultipleFailureException.java @@ -1,9 +1,13 @@ package org.junit.runners.model; +import java.io.PrintStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.TestCouldNotBeSkippedException; +import org.junit.internal.AssumptionViolatedException; import org.junit.internal.Throwables; /** @@ -17,12 +21,22 @@ public class MultipleFailureException extends Exception { /* * We have to use the f prefix until the next major release to ensure * serialization compatibility. - * See https://github.com/junit-team/junit/issues/976 + * See https://github.com/junit-team/junit4/issues/976 */ private final List<Throwable> fErrors; public MultipleFailureException(List<Throwable> errors) { - this.fErrors = new ArrayList<Throwable>(errors); + if (errors.isEmpty()) { + throw new IllegalArgumentException( + "List of Throwables must not be empty"); + } + this.fErrors = new ArrayList<Throwable>(errors.size()); + for (Throwable error : errors) { + if (error instanceof AssumptionViolatedException) { + error = new TestCouldNotBeSkippedException((AssumptionViolatedException) error); + } + fErrors.add(error); + } } public List<Throwable> getFailures() { @@ -34,11 +48,32 @@ public class MultipleFailureException extends Exception { StringBuilder sb = new StringBuilder( String.format("There were %d errors:", fErrors.size())); for (Throwable e : fErrors) { - sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); + sb.append(String.format("%n %s(%s)", e.getClass().getName(), e.getMessage())); } return sb.toString(); } + @Override + public void printStackTrace() { + for (Throwable e: fErrors) { + e.printStackTrace(); + } + } + + @Override + public void printStackTrace(PrintStream s) { + for (Throwable e: fErrors) { + e.printStackTrace(s); + } + } + + @Override + public void printStackTrace(PrintWriter s) { + for (Throwable e: fErrors) { + e.printStackTrace(s); + } + } + /** * Asserts that a list of throwables is empty. If it isn't empty, * will throw {@link MultipleFailureException} (if there are diff --git a/src/main/java/org/junit/runners/model/RunnerBuilder.java b/src/main/java/org/junit/runners/model/RunnerBuilder.java index 7d3eee3..ba7c9e2 100644 --- a/src/main/java/org/junit/runners/model/RunnerBuilder.java +++ b/src/main/java/org/junit/runners/model/RunnerBuilder.java @@ -6,7 +6,11 @@ import java.util.List; import java.util.Set; import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Description; +import org.junit.runner.OrderWith; import org.junit.runner.Runner; +import org.junit.runner.manipulation.InvalidOrderingException; +import org.junit.runner.manipulation.Ordering; /** * A RunnerBuilder is a strategy for constructing runners for classes. @@ -49,19 +53,39 @@ public abstract class RunnerBuilder { public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; /** - * Always returns a runner, even if it is just one that prints an error instead of running tests. + * Always returns a runner for the given test class. + * + * <p>In case of an exception a runner will be returned that prints an error instead of running + * tests. + * + * <p>Note that some of the internal JUnit implementations of RunnerBuilder will return + * {@code null} from this method, but no RunnerBuilder passed to a Runner constructor will + * return {@code null} from this method. * * @param testClass class to be run * @return a Runner */ public Runner safeRunnerForClass(Class<?> testClass) { try { - return runnerForClass(testClass); + Runner runner = runnerForClass(testClass); + if (runner != null) { + configureRunner(runner); + } + return runner; } catch (Throwable e) { return new ErrorReportingRunner(testClass, e); } } + private void configureRunner(Runner runner) throws InvalidOrderingException { + Description description = runner.getDescription(); + OrderWith orderWith = description.getAnnotation(OrderWith.class); + if (orderWith != null) { + Ordering ordering = Ordering.definedBy(orderWith.value(), description); + ordering.apply(runner); + } + } + Class<?> addParent(Class<?> parent) throws InitializationError { if (!parents.add(parent)) { throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); @@ -96,7 +120,7 @@ public abstract class RunnerBuilder { } private List<Runner> runners(Class<?>[] children) { - ArrayList<Runner> runners = new ArrayList<Runner>(); + List<Runner> runners = new ArrayList<Runner>(); for (Class<?> each : children) { Runner childRunner = safeRunnerForClass(each); if (childRunner != null) { diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java index c8a544d..5962c2b 100755..100644 --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -84,20 +84,21 @@ public class TestClass implements Annotatable { for (Annotation each : member.getAnnotations()) { Class<? extends Annotation> type = each.annotationType(); List<T> members = getAnnotatedMembers(map, type, true); - if (member.isShadowedBy(members)) { + T memberToAdd = member.handlePossibleBridgeMethod(members); + if (memberToAdd == null) { return; } if (runsTopToBottom(type)) { - members.add(0, member); + members.add(0, memberToAdd); } else { - members.add(member); + members.add(memberToAdd); } } } private static <T extends FrameworkMember<T>> Map<Class<? extends Annotation>, List<T>> makeDeeplyUnmodifiable(Map<Class<? extends Annotation>, List<T>> source) { - LinkedHashMap<Class<? extends Annotation>, List<T>> copy = + Map<Class<? extends Annotation>, List<T>> copy = new LinkedHashMap<Class<? extends Annotation>, List<T>>(); for (Map.Entry<Class<? extends Annotation>, List<T>> entry : source.entrySet()) { copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); @@ -168,7 +169,7 @@ public class TestClass implements Annotatable { } private static List<Class<?>> getSuperClasses(Class<?> testClass) { - ArrayList<Class<?>> results = new ArrayList<Class<?>>(); + List<Class<?>> results = new ArrayList<Class<?>>(); Class<?> current = testClass; while (current != null) { results.add(current); @@ -224,24 +225,59 @@ public class TestClass implements Annotatable { public <T> List<T> getAnnotatedFieldValues(Object test, Class<? extends Annotation> annotationClass, Class<T> valueClass) { - List<T> results = new ArrayList<T>(); + final List<T> results = new ArrayList<T>(); + collectAnnotatedFieldValues(test, annotationClass, valueClass, + new MemberValueConsumer<T>() { + public void accept(FrameworkMember<?> member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the fields annotated with the specified annotation and having the specified type, + * retrieves the values and passes those to the specified consumer. + * + * @since 4.13 + */ + public <T> void collectAnnotatedFieldValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass, + MemberValueConsumer<T> consumer) { for (FrameworkField each : getAnnotatedFields(annotationClass)) { try { Object fieldValue = each.get(test); if (valueClass.isInstance(fieldValue)) { - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (IllegalAccessException e) { throw new RuntimeException( "How did getFields return a field we couldn't access?", e); } } - return results; } public <T> List<T> getAnnotatedMethodValues(Object test, Class<? extends Annotation> annotationClass, Class<T> valueClass) { - List<T> results = new ArrayList<T>(); + final List<T> results = new ArrayList<T>(); + collectAnnotatedMethodValues(test, annotationClass, valueClass, + new MemberValueConsumer<T>() { + public void accept(FrameworkMember<?> member, T value) { + results.add(value); + } + }); + return results; + } + + /** + * Finds the methods annotated with the specified annotation and returning the specified type, + * invokes it and pass the return value to the specified consumer. + * + * @since 4.13 + */ + public <T> void collectAnnotatedMethodValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass, + MemberValueConsumer<T> consumer) { for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) { try { /* @@ -254,14 +290,13 @@ public class TestClass implements Annotatable { */ if (valueClass.isAssignableFrom(each.getReturnType())) { Object fieldValue = each.invokeExplosively(test); - results.add(valueClass.cast(fieldValue)); + consumer.accept(each, valueClass.cast(fieldValue)); } } catch (Throwable e) { throw new RuntimeException( "Exception in " + each.getName(), e); } } - return results; } public boolean isPublic() { |