diff options
Diffstat (limited to 'src/main/java/org/junit/runner')
20 files changed, 629 insertions, 48 deletions
diff --git a/src/main/java/org/junit/runner/Computer.java b/src/main/java/org/junit/runner/Computer.java index 8bb4b20..18d0d31 100644 --- a/src/main/java/org/junit/runner/Computer.java +++ b/src/main/java/org/junit/runner/Computer.java @@ -30,7 +30,17 @@ public class Computer { public Runner runnerForClass(Class<?> testClass) throws Throwable { return getRunner(builder, testClass); } - }, classes); + }, classes) { + @Override + protected String getName() { + /* + * #1320 The generated suite is not based on a real class so + * only a 'null' description can be generated from it. This name + * will be overridden here. + */ + return "classes"; + } + }; } /** diff --git a/src/main/java/org/junit/runner/Describable.java b/src/main/java/org/junit/runner/Describable.java index 1514141..293fdb3 100644 --- a/src/main/java/org/junit/runner/Describable.java +++ b/src/main/java/org/junit/runner/Describable.java @@ -10,5 +10,5 @@ public interface Describable { /** * @return a {@link Description} showing the tests to be run by the receiver */ - public abstract Description getDescription(); + Description getDescription(); }
\ No newline at end of file diff --git a/src/main/java/org/junit/runner/Description.java b/src/main/java/org/junit/runner/Description.java index fe47eac..0846a1e 100644 --- a/src/main/java/org/junit/runner/Description.java +++ b/src/main/java/org/junit/runner/Description.java @@ -125,6 +125,17 @@ public class Description implements Serializable { } /** + * Create a <code>Description</code> named after <code>testClass</code> + * + * @param testClass A not null {@link Class} containing tests + * @param annotations meta-data about the test, for downstream interpreters + * @return a <code>Description</code> of <code>testClass</code> + */ + public static Description createSuiteDescription(Class<?> testClass, Annotation... annotations) { + return new Description(testClass, testClass.getName(), annotations); + } + + /** * Describes a Runner which runs no tests */ public static final Description EMPTY = new Description(null, "No Tests"); @@ -139,7 +150,7 @@ public class Description implements Serializable { /* * 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 Collection<Description> fChildren = new ConcurrentLinkedQueue<Description>(); private final String fDisplayName; diff --git a/src/main/java/org/junit/runner/FilterFactory.java b/src/main/java/org/junit/runner/FilterFactory.java index 57b4eaa..e2bfb73 100644 --- a/src/main/java/org/junit/runner/FilterFactory.java +++ b/src/main/java/org/junit/runner/FilterFactory.java @@ -16,7 +16,8 @@ public interface FilterFactory { /**
* Exception thrown if the {@link Filter} cannot be created.
*/
- public static class FilterNotCreatedException extends Exception {
+ @SuppressWarnings("serial")
+ class FilterNotCreatedException extends Exception {
public FilterNotCreatedException(Exception exception) {
super(exception.getMessage(), exception);
}
diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java index 434157c..3383407 100644 --- a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java +++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java @@ -85,13 +85,11 @@ class JUnitCommandLineParseResult { }
private String[] copyArray(String[] args, int from, int to) {
- ArrayList<String> result = new ArrayList<String>();
-
+ String[] result = new String[to - from];
for (int j = from; j != to; ++j) {
- result.add(args[j]);
+ result[j - from] = args[j];
}
-
- return result.toArray(new String[result.size()]);
+ return result;
}
void parseParameters(String[] args) {
diff --git a/src/main/java/org/junit/runner/OrderWith.java b/src/main/java/org/junit/runner/OrderWith.java new file mode 100644 index 0000000..e8470c9 --- /dev/null +++ b/src/main/java/org/junit/runner/OrderWith.java @@ -0,0 +1,28 @@ +package org.junit.runner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.runner.manipulation.Ordering; +import org.junit.validator.ValidateWith; + +/** + * When a test class is annotated with <code>@OrderWith</code> or extends a class annotated + * with <code>@OrderWith</code>, JUnit will order the tests in the test class (and child + * test classes, if any) using the ordering defined by the {@link Ordering} class. + * + * @since 4.13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@ValidateWith(OrderWithValidator.class) +public @interface OrderWith { + /** + * Gets a class that extends {@link Ordering}. The class must have a public no-arg constructor. + */ + Class<? extends Ordering.Factory> value(); +} diff --git a/src/main/java/org/junit/runner/OrderWithValidator.java b/src/main/java/org/junit/runner/OrderWithValidator.java new file mode 100644 index 0000000..f8eab25 --- /dev/null +++ b/src/main/java/org/junit/runner/OrderWithValidator.java @@ -0,0 +1,38 @@ +package org.junit.runner; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.List; + +import org.junit.FixMethodOrder; +import org.junit.runners.model.TestClass; +import org.junit.validator.AnnotationValidator; + +/** + * Validates that there are no errors in the use of the {@code OrderWith} + * annotation. If there is, a {@code Throwable} object will be added to the list + * of errors. + * + * @since 4.13 + */ +public final class OrderWithValidator extends AnnotationValidator { + + /** + * Adds to {@code errors} a throwable for each problem detected. Looks for + * {@code FixMethodOrder} annotations. + * + * @param testClass that is being validated + * @return A list of exceptions detected + * + * @since 4.13 + */ + @Override + public List<Exception> validateAnnotatedClass(TestClass testClass) { + if (testClass.getAnnotation(FixMethodOrder.class) != null) { + return singletonList( + new Exception("@FixMethodOrder cannot be combined with @OrderWith")); + } + return emptyList(); + } +} diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java index 79c0f1e..7b9a990 100644 --- a/src/main/java/org/junit/runner/Request.java +++ b/src/main/java/org/junit/runner/Request.java @@ -5,9 +5,11 @@ import java.util.Comparator; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.internal.requests.ClassRequest; import org.junit.internal.requests.FilterRequest; +import org.junit.internal.requests.OrderingRequest; import org.junit.internal.requests.SortingRequest; import org.junit.internal.runners.ErrorReportingRunner; import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Ordering; import org.junit.runners.model.InitializationError; /** @@ -71,12 +73,11 @@ public abstract class Request { */ public static Request classes(Computer computer, Class<?>... classes) { try { - AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); + AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(); Runner suite = computer.getSuite(builder, classes); return runner(suite); } catch (InitializationError e) { - throw new RuntimeException( - "Bug in saff's brain: Suite constructor, called as above, should always complete"); + return runner(new ErrorReportingRunner(e, classes)); } } @@ -132,13 +133,16 @@ public abstract class Request { } /** - * Returns a Request that only runs contains tests whose {@link Description} - * equals <code>desiredDescription</code> + * Returns a Request that only runs tests whose {@link Description} + * matches the given description. * - * @param desiredDescription {@link Description} of those tests that should be run + * <p>Returns an empty {@code Request} if {@code desiredDescription} is not a single test and filters all but the single + * test if {@code desiredDescription} is a single test.</p> + * + * @param desiredDescription {@code Description} of those tests that should be run * @return the filtered Request */ - public Request filterWith(final Description desiredDescription) { + public Request filterWith(Description desiredDescription) { return filterWith(Filter.matchMethodDescription(desiredDescription)); } @@ -149,15 +153,15 @@ public abstract class Request { * For example, here is code to run a test suite in alphabetical order: * <pre> * private static Comparator<Description> forward() { - * return new Comparator<Description>() { - * public int compare(Description o1, Description o2) { - * return o1.getDisplayName().compareTo(o2.getDisplayName()); - * } - * }; + * return new Comparator<Description>() { + * public int compare(Description o1, Description o2) { + * return o1.getDisplayName().compareTo(o2.getDisplayName()); + * } + * }; * } * * public static main() { - * new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward())); + * new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward())); * } * </pre> * @@ -167,4 +171,32 @@ public abstract class Request { public Request sortWith(Comparator<Description> comparator) { return new SortingRequest(this, comparator); } + + /** + * Returns a Request whose Tests can be run in a certain order, defined by + * <code>ordering</code> + * <p> + * For example, here is code to run a test suite in reverse order: + * <pre> + * private static Ordering reverse() { + * return new Ordering() { + * public List<Description> orderItems(Collection<Description> descriptions) { + * List<Description> ordered = new ArrayList<>(descriptions); + * Collections.reverse(ordered); + * return ordered; + * } + * } + * } + * + * public static main() { + * new JUnitCore().run(Request.aClass(AllTests.class).orderWith(reverse())); + * } + * </pre> + * + * @return a Request with ordered Tests + * @since 4.13 + */ + public Request orderWith(Ordering ordering) { + return new OrderingRequest(this, ordering); + } } diff --git a/src/main/java/org/junit/runner/Result.java b/src/main/java/org/junit/runner/Result.java index 73ad059..4b5f4a4 100644 --- a/src/main/java/org/junit/runner/Result.java +++ b/src/main/java/org/junit/runner/Result.java @@ -28,6 +28,7 @@ public class Result implements Serializable { ObjectStreamClass.lookup(SerializedForm.class).getFields(); private final AtomicInteger count; private final AtomicInteger ignoreCount; + private final AtomicInteger assumptionFailureCount; private final CopyOnWriteArrayList<Failure> failures; private final AtomicLong runTime; private final AtomicLong startTime; @@ -38,6 +39,7 @@ public class Result implements Serializable { public Result() { count = new AtomicInteger(); ignoreCount = new AtomicInteger(); + assumptionFailureCount = new AtomicInteger(); failures = new CopyOnWriteArrayList<Failure>(); runTime = new AtomicLong(); startTime = new AtomicLong(); @@ -46,34 +48,35 @@ public class Result implements Serializable { private Result(SerializedForm serializedForm) { count = serializedForm.fCount; ignoreCount = serializedForm.fIgnoreCount; + assumptionFailureCount = serializedForm.assumptionFailureCount; failures = new CopyOnWriteArrayList<Failure>(serializedForm.fFailures); runTime = new AtomicLong(serializedForm.fRunTime); startTime = new AtomicLong(serializedForm.fStartTime); } /** - * @return the number of tests run + * Returns the number of tests run */ public int getRunCount() { return count.get(); } /** - * @return the number of tests that failed during the run + * Returns the number of tests that failed during the run */ public int getFailureCount() { return failures.size(); } /** - * @return the number of milliseconds it took to run the entire suite to run + * Returns the number of milliseconds it took to run the entire suite to run */ public long getRunTime() { return runTime.get(); } /** - * @return the {@link Failure}s describing tests that failed and the problems they encountered + * Returns the {@link Failure}s describing tests that failed and the problems they encountered */ public List<Failure> getFailures() { return failures; @@ -87,6 +90,20 @@ public class Result implements Serializable { } /** + * Returns the number of tests skipped because of an assumption failure + * + * @throws UnsupportedOperationException if the result was serialized in a version before JUnit 4.13 + * @since 4.13 + */ + public int getAssumptionFailureCount() { + if (assumptionFailureCount == null) { + throw new UnsupportedOperationException( + "Result was serialized from a version of JUnit that doesn't support this method"); + } + return assumptionFailureCount.get(); + } + + /** * @return <code>true</code> if all tests succeeded */ public boolean wasSuccessful() { @@ -137,7 +154,7 @@ public class Result implements Serializable { @Override public void testAssumptionFailure(Failure failure) { - // do nothing: same as passing (for 4.5; may change in 4.6) + assumptionFailureCount.getAndIncrement(); } } @@ -156,6 +173,7 @@ public class Result implements Serializable { private static final long serialVersionUID = 1L; private final AtomicInteger fCount; private final AtomicInteger fIgnoreCount; + private final AtomicInteger assumptionFailureCount; private final List<Failure> fFailures; private final long fRunTime; private final long fStartTime; @@ -163,6 +181,7 @@ public class Result implements Serializable { public SerializedForm(Result result) { fCount = result.count; fIgnoreCount = result.ignoreCount; + assumptionFailureCount = result.assumptionFailureCount; fFailures = Collections.synchronizedList(new ArrayList<Failure>(result.failures)); fRunTime = result.runTime.longValue(); fStartTime = result.startTime.longValue(); @@ -172,6 +191,7 @@ public class Result implements Serializable { private SerializedForm(ObjectInputStream.GetField fields) throws IOException { fCount = (AtomicInteger) fields.get("fCount", null); fIgnoreCount = (AtomicInteger) fields.get("fIgnoreCount", null); + assumptionFailureCount = (AtomicInteger) fields.get("assumptionFailureCount", null); fFailures = (List<Failure>) fields.get("fFailures", null); fRunTime = fields.get("fRunTime", 0L); fStartTime = fields.get("fStartTime", 0L); @@ -184,6 +204,7 @@ public class Result implements Serializable { fields.put("fFailures", fFailures); fields.put("fRunTime", fRunTime); fields.put("fStartTime", fStartTime); + fields.put("assumptionFailureCount", assumptionFailureCount); s.writeFields(); } diff --git a/src/main/java/org/junit/runner/manipulation/Alphanumeric.java b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java new file mode 100644 index 0000000..8388d21 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java @@ -0,0 +1,27 @@ +package org.junit.runner.manipulation; + +import java.util.Comparator; + +import org.junit.runner.Description; + +/** + * A sorter that orders tests alphanumerically by test name. + * + * @since 4.13 + */ +public final class Alphanumeric extends Sorter implements Ordering.Factory { + + public Alphanumeric() { + super(COMPARATOR); + } + + public Ordering create(Context context) { + return this; + } + + private static final Comparator<Description> COMPARATOR = new Comparator<Description>() { + public int compare(Description o1, Description o2) { + return o1.getDisplayName().compareTo(o2.getDisplayName()); + } + }; +} diff --git a/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java new file mode 100644 index 0000000..d9d60f7 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java @@ -0,0 +1,21 @@ +package org.junit.runner.manipulation; + +/** + * Thrown when an ordering does something invalid (like remove or add children) + * + * @since 4.13 + */ +public class InvalidOrderingException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidOrderingException() { + } + + public InvalidOrderingException(String message) { + super(message); + } + + public InvalidOrderingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Orderable.java b/src/main/java/org/junit/runner/manipulation/Orderable.java new file mode 100644 index 0000000..9a12a3b --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Orderable.java @@ -0,0 +1,21 @@ +package org.junit.runner.manipulation; + +/** + * Interface for runners that allow ordering of tests. + * + * <p>Beware of using this interface to cope with order dependencies between tests. + * Tests that are isolated from each other are less expensive to maintain and + * can be run individually. + * + * @since 4.13 + */ +public interface Orderable extends Sortable { + + /** + * Orders the tests using <code>orderer</code> + * + * @throws InvalidOrderingException if orderer does something invalid (like remove or add + * children) + */ + void order(Orderer orderer) throws InvalidOrderingException; +} diff --git a/src/main/java/org/junit/runner/manipulation/Orderer.java b/src/main/java/org/junit/runner/manipulation/Orderer.java new file mode 100644 index 0000000..eb13054 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Orderer.java @@ -0,0 +1,62 @@ +package org.junit.runner.manipulation; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.runner.Description; + +/** + * Orders tests. + * + * @since 4.13 + */ +public final class Orderer { + private final Ordering ordering; + + Orderer(Ordering delegate) { + this.ordering = delegate; + } + + /** + * Orders the descriptions. + * + * @return descriptions in order + */ + public List<Description> order(Collection<Description> descriptions) + throws InvalidOrderingException { + List<Description> inOrder = ordering.orderItems( + Collections.unmodifiableCollection(descriptions)); + if (!ordering.validateOrderingIsCorrect()) { + return inOrder; + } + + Set<Description> uniqueDescriptions = new HashSet<Description>(descriptions); + if (!uniqueDescriptions.containsAll(inOrder)) { + throw new InvalidOrderingException("Ordering added items"); + } + Set<Description> resultAsSet = new HashSet<Description>(inOrder); + if (resultAsSet.size() != inOrder.size()) { + throw new InvalidOrderingException("Ordering duplicated items"); + } else if (!resultAsSet.containsAll(uniqueDescriptions)) { + throw new InvalidOrderingException("Ordering removed items"); + } + + return inOrder; + } + + /** + * Order the tests in <code>target</code>. + * + * @throws InvalidOrderingException if ordering does something invalid (like remove or add + * children) + */ + public void apply(Object target) throws InvalidOrderingException { + if (target instanceof Orderable) { + Orderable orderable = (Orderable) target; + orderable.order(this); + } + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Ordering.java b/src/main/java/org/junit/runner/manipulation/Ordering.java new file mode 100644 index 0000000..0d0ce93 --- /dev/null +++ b/src/main/java/org/junit/runner/manipulation/Ordering.java @@ -0,0 +1,172 @@ +package org.junit.runner.manipulation; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.junit.runner.Description; +import org.junit.runner.OrderWith; + +/** + * Reorders tests. An {@code Ordering} can reverse the order of tests, sort the + * order or even shuffle the order. + * + * <p>In general you will not need to use a <code>Ordering</code> directly. + * Instead, use {@link org.junit.runner.Request#orderWith(Ordering)}. + * + * @since 4.13 + */ +public abstract class Ordering { + private static final String CONSTRUCTOR_ERROR_FORMAT + = "Ordering class %s should have a public constructor with signature " + + "%s(Ordering.Context context)"; + + /** + * Creates an {@link Ordering} that shuffles the items using the given + * {@link Random} instance. + */ + public static Ordering shuffledBy(final Random random) { + return new Ordering() { + @Override + boolean validateOrderingIsCorrect() { + return false; + } + + @Override + protected List<Description> orderItems(Collection<Description> descriptions) { + List<Description> shuffled = new ArrayList<Description>(descriptions); + Collections.shuffle(shuffled, random); + return shuffled; + } + }; + } + + /** + * Creates an {@link Ordering} from the given factory class. The class must have a public no-arg + * constructor. + * + * @param factoryClass class to use to create the ordering + * @param annotatedTestClass test class that is annotated with {@link OrderWith}. + * @throws InvalidOrderingException if the instance could not be created + */ + public static Ordering definedBy( + Class<? extends Ordering.Factory> factoryClass, Description annotatedTestClass) + throws InvalidOrderingException { + if (factoryClass == null) { + throw new NullPointerException("factoryClass cannot be null"); + } + if (annotatedTestClass == null) { + throw new NullPointerException("annotatedTestClass cannot be null"); + } + + Ordering.Factory factory; + try { + Constructor<? extends Ordering.Factory> constructor = factoryClass.getConstructor(); + factory = constructor.newInstance(); + } catch (NoSuchMethodException e) { + throw new InvalidOrderingException(String.format( + CONSTRUCTOR_ERROR_FORMAT, + getClassName(factoryClass), + factoryClass.getSimpleName())); + } catch (Exception e) { + throw new InvalidOrderingException( + "Could not create ordering for " + annotatedTestClass, e); + } + return definedBy(factory, annotatedTestClass); + } + + /** + * Creates an {@link Ordering} from the given factory. + * + * @param factory factory to use to create the ordering + * @param annotatedTestClass test class that is annotated with {@link OrderWith}. + * @throws InvalidOrderingException if the instance could not be created + */ + public static Ordering definedBy( + Ordering.Factory factory, Description annotatedTestClass) + throws InvalidOrderingException { + if (factory == null) { + throw new NullPointerException("factory cannot be null"); + } + if (annotatedTestClass == null) { + throw new NullPointerException("annotatedTestClass cannot be null"); + } + + return factory.create(new Ordering.Context(annotatedTestClass)); + } + + private static String getClassName(Class<?> clazz) { + String name = clazz.getCanonicalName(); + if (name == null) { + return clazz.getName(); + } + return name; + } + + /** + * Order the tests in <code>target</code> using this ordering. + * + * @throws InvalidOrderingException if ordering does something invalid (like remove or add + * children) + */ + public void apply(Object target) throws InvalidOrderingException { + /* + * Note that some subclasses of Ordering override apply(). The Sorter + * subclass of Ordering overrides apply() to apply the sort (this is + * done because sorting is more efficient than ordering). + */ + if (target instanceof Orderable) { + Orderable orderable = (Orderable) target; + orderable.order(new Orderer(this)); + } + } + + /** + * Returns {@code true} if this ordering could produce invalid results (i.e. + * if it could add or remove values). + */ + boolean validateOrderingIsCorrect() { + return true; + } + + /** + * Implemented by sub-classes to order the descriptions. + * + * @return descriptions in order + */ + protected abstract List<Description> orderItems(Collection<Description> descriptions); + + /** Context about the ordering being applied. */ + public static class Context { + private final Description description; + + /** + * Gets the description for the top-level target being ordered. + */ + public Description getTarget() { + return description; + } + + private Context(Description description) { + this.description = description; + } + } + + /** + * Factory for creating {@link Ordering} instances. + * + * <p>For a factory to be used with {@code @OrderWith} it needs to have a public no-arg + * constructor. + */ + public interface Factory { + /** + * Creates an Ordering instance using the given context. Implementations + * of this method that do not need to use the context can return the + * same instance every time. + */ + Ordering create(Context context); + } +} diff --git a/src/main/java/org/junit/runner/manipulation/Sortable.java b/src/main/java/org/junit/runner/manipulation/Sortable.java index 9ac864c..0c59f33 100644 --- a/src/main/java/org/junit/runner/manipulation/Sortable.java +++ b/src/main/java/org/junit/runner/manipulation/Sortable.java @@ -15,6 +15,6 @@ public interface Sortable { * * @param sorter the {@link Sorter} to use for sorting the tests */ - public void sort(Sorter sorter); + void sort(Sorter sorter); } diff --git a/src/main/java/org/junit/runner/manipulation/Sorter.java b/src/main/java/org/junit/runner/manipulation/Sorter.java index 20192d0..4b5274c 100644 --- a/src/main/java/org/junit/runner/manipulation/Sorter.java +++ b/src/main/java/org/junit/runner/manipulation/Sorter.java @@ -1,16 +1,21 @@ package org.junit.runner.manipulation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import org.junit.runner.Description; /** * A <code>Sorter</code> orders tests. In general you will not need - * to use a <code>Sorter</code> directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * to use a <code>Sorter</code> directly. Instead, use + * {@link org.junit.runner.Request#sortWith(Comparator)}. * * @since 4.0 */ -public class Sorter implements Comparator<Description> { +public class Sorter extends Ordering implements Comparator<Description> { /** * NULL is a <code>Sorter</code> that leaves elements in an undefined order */ @@ -27,17 +32,26 @@ public class Sorter implements Comparator<Description> { * to sort tests * * @param comparator the {@link Comparator} to use when sorting tests + * @since 4.0 */ public Sorter(Comparator<Description> comparator) { this.comparator = comparator; } /** - * Sorts the test in <code>runner</code> using <code>comparator</code> + * Sorts the tests in <code>target</code> using <code>comparator</code>. + * + * @since 4.0 */ - public void apply(Object object) { - if (object instanceof Sortable) { - Sortable sortable = (Sortable) object; + @Override + public void apply(Object target) { + /* + * Note that all runners that are Orderable are also Sortable (because + * Orderable extends Sortable). Sorting is more efficient than ordering, + * so we override the parent behavior so we sort instead. + */ + if (target instanceof Sortable) { + Sortable sortable = (Sortable) target; sortable.sort(this); } } @@ -45,4 +59,32 @@ public class Sorter implements Comparator<Description> { public int compare(Description o1, Description o2) { return comparator.compare(o1, o2); } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + @Override + protected final List<Description> orderItems(Collection<Description> descriptions) { + /* + * In practice, we will never get here--Sorters do their work in the + * compare() method--but the Liskov substitution principle demands that + * we obey the general contract of Orderable. Luckily, it's trivial to + * implement. + */ + List<Description> sorted = new ArrayList<Description>(descriptions); + Collections.sort(sorted, this); // Note: it would be incorrect to pass in "comparator" + return sorted; + } + + /** + * {@inheritDoc} + * + * @since 4.13 + */ + @Override + boolean validateOrderingIsCorrect() { + return false; + } } diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java index c03b4c1..4551302 100644 --- a/src/main/java/org/junit/runner/notification/Failure.java +++ b/src/main/java/org/junit/runner/notification/Failure.java @@ -1,9 +1,8 @@ package org.junit.runner.notification; -import java.io.PrintWriter; import java.io.Serializable; -import java.io.StringWriter; +import org.junit.internal.Throwables; import org.junit.runner.Description; /** @@ -21,7 +20,7 @@ public class Failure implements Serializable { /* * 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 Description fDescription; private final Throwable fThrownException; @@ -65,15 +64,19 @@ public class Failure implements Serializable { } /** - * Convenience method - * - * @return the printed form of the exception + * Gets the printed form of the exception and its stack trace. */ public String getTrace() { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - getException().printStackTrace(writer); - return stringWriter.toString(); + return Throwables.getStacktrace(getException()); + } + + /** + * Gets a the printed form of the exception, with a trimmed version of the stack trace. + * This method will attempt to filter out frames of the stack trace that are below + * the test method call. + */ + public String getTrimmedTrace() { + return Throwables.getTrimmedStackTrace(getException()); } /** diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java index db9d8c1..d7cac00 100644 --- a/src/main/java/org/junit/runner/notification/RunListener.java +++ b/src/main/java/org/junit/runner/notification/RunListener.java @@ -70,6 +70,34 @@ public class RunListener { } /** + * Called when a test suite is about to be started. If this method is + * called for a given {@link Description}, then {@link #testSuiteFinished(Description)} + * will also be called for the same {@code Description}. + * + * <p>Note that not all runners will call this method, so runners should + * be prepared to handle {@link #testStarted(Description)} calls for tests + * where there was no corresponding {@code testSuiteStarted()} call for + * the parent {@code Description}. + * + * @param description the description of the test suite that is about to be run + * (generally a class name) + * @since 4.13 + */ + public void testSuiteStarted(Description description) throws Exception { + } + + /** + * Called when a test suite has finished, whether the test suite succeeds or fails. + * This method will not be called for a given {@link Description} unless + * {@link #testSuiteStarted(Description)} was called for the same @code Description}. + * + * @param description the description of the test suite that just ran + * @since 4.13 + */ + public void testSuiteFinished(Description description) throws Exception { + } + + /** * Called when an atomic test is about to be started. * * @param description the description of the test that is about to be run diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java index 6875f76..752fa3b 100644 --- a/src/main/java/org/junit/runner/notification/RunNotifier.java +++ b/src/main/java/org/junit/runner/notification/RunNotifier.java @@ -65,8 +65,8 @@ public class RunNotifier { void run() { int capacity = currentListeners.size(); - ArrayList<RunListener> safeListeners = new ArrayList<RunListener>(capacity); - ArrayList<Failure> failures = new ArrayList<Failure>(capacity); + List<RunListener> safeListeners = new ArrayList<RunListener>(capacity); + List<Failure> failures = new ArrayList<Failure>(capacity); for (RunListener listener : currentListeners) { try { notifyListener(listener); @@ -78,7 +78,7 @@ public class RunNotifier { fireTestFailures(safeListeners, failures); } - abstract protected void notifyListener(RunListener each) throws Exception; + protected abstract void notifyListener(RunListener each) throws Exception; } /** @@ -106,6 +106,41 @@ public class RunNotifier { } /** + * Invoke to tell listeners that a test suite is about to start. Runners are strongly + * encouraged--but not required--to call this method. If this method is called for + * a given {@link Description} then {@link #fireTestSuiteFinished(Description)} MUST + * be called for the same {@code Description}. + * + * @param description the description of the suite test (generally a class name) + * @since 4.13 + */ + public void fireTestSuiteStarted(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testSuiteStarted(description); + } + }.run(); + } + + /** + * Invoke to tell listeners that a test suite is about to finish. Always invoke + * this method if you invoke {@link #fireTestSuiteStarted(Description)} + * as listeners are likely to expect them to come in pairs. + * + * @param description the description of the suite test (generally a class name) + * @since 4.13 + */ + public void fireTestSuiteFinished(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testSuiteFinished(description); + } + }.run(); + } + + /** * Invoke to tell listeners that an atomic test is about to start. * * @param description the description of the atomic test (generally a class and method name) diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java index c53c1ee..400fed8 100644 --- a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java +++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java @@ -10,7 +10,7 @@ import org.junit.runner.Result; * <p>This class synchronizes all listener calls on a RunNotifier instance. This is done because * prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier, * so no two listeners were ever called concurrently. If we instead made the methods here - * sychronized, clients that added multiple listeners that called common code might see + * synchronized, clients that added multiple listeners that called common code might see * issues due to the reduced synchronization. * * @author Tibor Digana (tibor17) @@ -43,6 +43,37 @@ final class SynchronizedRunListener extends RunListener { } } + /** + * {@inheritDoc} + * <p/> + * Synchronized decorator for {@link RunListener#testSuiteStarted(Description)}. + * @param description the description of the test suite that is about to be run + * (generally a class name). + * @throws Exception if any occurs. + * @since 4.13 + */ + @Override + public void testSuiteStarted(Description description) throws Exception { + synchronized (monitor) { + listener.testSuiteStarted(description); + } + } + + /** + * {@inheritDoc} + * <p/> + * Synchronized decorator for {@link RunListener#testSuiteFinished(Description)}. + * @param description the description of the test suite that just ran. + * @throws Exception + * @since 4.13 + */ + @Override + public void testSuiteFinished(Description description) throws Exception { + synchronized (monitor) { + listener.testSuiteFinished(description); + } + } + @Override public void testStarted(Description description) throws Exception { synchronized (monitor) { |