Skip to content

Commit

Permalink
SortMethodsWith allows the user to choose the order of execution of t…
Browse files Browse the repository at this point in the history
…he methods within a test class.

The default order of execution of JUnit tests within a class
is deterministic but not predictable. Before 4.11, the
behaviour was to run the test methods in byte code order,
which pre-Java 7 was mostly predictable. Java 7 (and some
previous versions), does not guaranteee the order of execution,
which can change from run to run, so a deterministic sort was introduced.
As a rule, test method execution should be independent of
one another. However, there may be a number of dependent
tests either through error or by design. This class
allows the user to specify the order of execution of test methods.

There are four possibilities:

MethodSorters.DEFAULT: the default value, deterministic, but not predictable
MethodSorters.JVM: the order in which the tests are returned by the JVM, i.e. there is no sorting done
MethodSorters.NAME_ASC: sorted in order of method name, ascending
MethodSorters.NAME_DESC: sorter in order of method name, descending
  • Loading branch information
matthewfarwell committed Feb 19, 2012
1 parent db9a456 commit c610a49
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 18 deletions.
47 changes: 47 additions & 0 deletions src/main/java/org/junit/SortMethodsWith.java
@@ -0,0 +1,47 @@
package org.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.runners.MethodSorters;

/**
* SortMethodsWith allows the user to choose the order of execution of the methods within a test class.
* <br/>
* <br/>
* The default order of execution of JUnit tests within a class is deterministic but not predictable.
* Before 4.11, the behaviour was to run the test methods in byte code order, which pre-Java 7 was mostly predictable.
* Java 7 (and some previous versions), does not guaranteee the order of execution, which can change from run to run,
* so a deterministic sort was introduced.
* <br/>
* As a rule, test method execution should be independent of one another. However, there may be a number of dependent tests
* either through error or by design. This class allows the user to specify the order of execution of test methods.
* <br/>
* There are four possibilities:
* <ul>
* <li>MethodSorters.DEFAULT: the default value, deterministic, but not predictable</li>
* <li>MethodSorters.JVM: the order in which the tests are returned by the JVM, i.e. there is no sorting done</li>
* <li>MethodSorters.NAME_ASC: sorted in order of method name, ascending</li>
* <li>MethodSorters.NAME_DESC: sorter in order of method name, descending</li>
* </ul>
*
* Here is an example:
*
* <pre>
* &#064;SortMethodsWith(MethodSorters.NAME_ASC)
* public class MyTest {
* }
* </pre>
*
* @see org.junit.runners.MethodSorters
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SortMethodsWith {
/**
* Optionally specify <code>sorter</code> to have the methods executed in a particular order
*/
MethodSorters value() default MethodSorters.DEFAULT;
}
59 changes: 50 additions & 9 deletions src/main/java/org/junit/internal/MethodSorter.java
Expand Up @@ -4,32 +4,73 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;


import org.junit.SortMethodsWith;

public class MethodSorter { public class MethodSorter {
/**
* DEFAULT sort order
*/
public static Comparator<Method> DEFAULT = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
int i1 = m1.getName().hashCode();
int i2 = m2.getName().hashCode();
return i1 != i2 ? i1 - i2 : m1.toString().compareTo(m2.toString());
}
};

/**
* Method name ascending sort order
*/
public static Comparator<Method> NAME_ASC = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
return MethodSorter.compare(m1.getName(), m2.getName());
}
};


private static int compare(String s1, String s2) {
return s1.compareTo(s2);
}

/** /**
* Gets declared methods of a class in a predictable order. * Method name descending sort order
*/
public static Comparator<Method> NAME_DESC = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
return MethodSorter.compare(m1.getName(), m2.getName()) * -1;
}
};

/**
* Gets declared methods of a class in a predictable order, unless @SortMethodsWith(MethodSorters.JVM) is specified.
*
* Using the JVM order is unwise since the Java platform does not * Using the JVM order is unwise since the Java platform does not
* specify any particular order, and in fact JDK 7 returns a more or less * specify any particular order, and in fact JDK 7 returns a more or less
* random order; well-written test code would not assume any order, but some * random order; well-written test code would not assume any order, but some
* does, and a predictable failure is better than a random failure on * does, and a predictable failure is better than a random failure on
* certain platforms. Uses an unspecified but deterministic order. * certain platforms. By default, uses an unspecified but deterministic order.
* @param clazz a class * @param clazz a class
* @return same as {@link Class#getDeclaredMethods} but sorted * @return same as {@link Class#getDeclaredMethods} but sorted
* @see <a href="http://bugs.sun.com/view_bug.do?bug_id=7023180">JDK * @see <a href="http://bugs.sun.com/view_bug.do?bug_id=7023180">JDK
* (non-)bug #7023180</a> * (non-)bug #7023180</a>
*/ */
public static Method[] getDeclaredMethods(Class<?> clazz) { public static Method[] getDeclaredMethods(Class<?> clazz) {
Comparator<Method> comparator = getSorter(clazz.getAnnotation(SortMethodsWith.class));

Method[] methods = clazz.getDeclaredMethods(); Method[] methods = clazz.getDeclaredMethods();
Arrays.sort(methods, new Comparator<Method>() { if (comparator != null) {
@Override public int compare(Method m1, Method m2) { Arrays.sort(methods, comparator);
int i1 = m1.getName().hashCode(); }
int i2 = m2.getName().hashCode();
return i1 != i2 ? i1 - i2 : m1.toString().compareTo(m2.toString());
}
});
return methods; return methods;
} }


private MethodSorter() {} private MethodSorter() {}


private static Comparator<Method> getSorter(SortMethodsWith sortMethodsWith) {
if (sortMethodsWith == null) {
return DEFAULT;
}

return sortMethodsWith.value().getComparator();
}
} }
30 changes: 30 additions & 0 deletions src/main/java/org/junit/runners/MethodSorters.java
@@ -0,0 +1,30 @@
package org.junit.runners;

import java.lang.reflect.Method;
import java.util.Comparator;

import org.junit.internal.MethodSorter;

/**
* Sort the methods into a specified execution order
*/
public enum MethodSorters {
/** Name ascending */
NAME_ASC(MethodSorter.NAME_ASC),
/** Name descending */
NAME_DESC(MethodSorter.NAME_DESC),
/** default JVM, (no sort) */
JVM(null),
/** Default, deterministic but not predictable */
DEFAULT(MethodSorter.DEFAULT);

private final Comparator<Method> fComparator;

private MethodSorters(Comparator<Method> comparator) {
this.fComparator= comparator;
}

public Comparator<Method> getComparator() {
return fComparator;
}
}
83 changes: 74 additions & 9 deletions src/test/java/org/junit/internal/MethodSorterTest.java
@@ -1,34 +1,99 @@
package org.junit.internal; package org.junit.internal;


import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;

import org.junit.SortMethodsWith;
import org.junit.Test; import org.junit.Test;
import org.junit.runners.MethodSorters;
import static org.junit.Assert.*; import static org.junit.Assert.*;


public class MethodSorterTest { public class MethodSorterTest {
private static class Dummy {
Object alpha(int i, double d, Thread t) {return null;}
void beta(int[][] x) {}
int gamma() {return 0;}
void gamma(boolean b) {}
void delta() {}
void epsilon() {}
}
private static class Super {
void testOne() {}
}
private static class Sub extends Super {
void testTwo() {}
}

private String toString(Class<?> clazz, Method[] methods) {
return Arrays.toString(methods).replace(clazz.getName() + '.', "");
}

private String declaredMethods(Class<?> clazz) {
return toString(clazz, MethodSorter.getDeclaredMethods(clazz));
}


@Test public void getDeclaredMethods() throws Exception { @Test public void getMethodsNullSorter() throws Exception {
assertEquals("[void epsilon(), void beta(int[][]), java.lang.Object alpha(int,double,java.lang.Thread), void delta(), int gamma(), void gamma(boolean)]", declaredMethods(Dummy.class)); assertEquals("[void epsilon(), void beta(int[][]), java.lang.Object alpha(int,double,java.lang.Thread), void delta(), int gamma(), void gamma(boolean)]", declaredMethods(Dummy.class));
assertEquals("[void testOne()]", declaredMethods(Super.class)); assertEquals("[void testOne()]", declaredMethods(Super.class));
assertEquals("[void testTwo()]", declaredMethods(Sub.class)); assertEquals("[void testTwo()]", declaredMethods(Sub.class));
} }


private static String declaredMethods(Class<?> c) { @SortMethodsWith(MethodSorters.DEFAULT)
return Arrays.toString(MethodSorter.getDeclaredMethods(c)).replace(c.getName() + '.', ""); private static class DummySortWithDefault {
Object alpha(int i, double d, Thread t) {return null;}
void beta(int[][] x) {}
int gamma() {return 0;}
void gamma(boolean b) {}
void delta() {}
void epsilon() {}
} }


private static class Dummy { @Test public void testDefaultSorter() {
assertEquals("[void epsilon(), void beta(int[][]), java.lang.Object alpha(int,double,java.lang.Thread), void delta(), int gamma(), void gamma(boolean)]", declaredMethods(DummySortWithDefault.class));
}

@SortMethodsWith(MethodSorters.JVM)
private static class DummySortJvm {
Object alpha(int i, double d, Thread t) {return null;} Object alpha(int i, double d, Thread t) {return null;}
void beta(int[][] x) {} void beta(int[][] x) {}
int gamma() {return 0;} int gamma() {return 0;}
void gamma(boolean b) {} void gamma(boolean b) {}
void delta() {} void delta() {}
void epsilon() {} void epsilon() {}
} }
private static class Super {
void testOne() {} @Test public void testSortWithJvm() {
Class<?> clazz = DummySortJvm.class;
String actual = toString(clazz, clazz.getDeclaredMethods());

assertEquals(actual, declaredMethods(clazz));
}

@SortMethodsWith(MethodSorters.NAME_ASC)
private static class DummySortWithNameAsc {
Object alpha(int i, double d, Thread t) {return null;}
void beta(int[][] x) {}
int gamma() {return 0;}
void gamma(boolean b) {}
void delta() {}
void epsilon() {}
} }
private static class Sub extends Super {
void testTwo() {} @Test public void testNameAsc() {
assertEquals("[java.lang.Object alpha(int,double,java.lang.Thread), void beta(int[][]), void delta(), void epsilon(), int gamma(), void gamma(boolean)]", declaredMethods(DummySortWithNameAsc.class));
}

@SortMethodsWith(MethodSorters.NAME_DESC)
private static class DummySortWithNameDesc {
Object alpha(int i, double d, Thread t) {return null;}
void beta(int[][] x) {}
int gamma() {return 0;}
void gamma(boolean b) {}
void delta() {}
void epsilon() {}
} }


@Test public void testNameDesc() {
assertEquals("[int gamma(), void gamma(boolean), void epsilon(), void delta(), void beta(int[][]), java.lang.Object alpha(int,double,java.lang.Thread)]", declaredMethods(DummySortWithNameDesc.class));
}
} }

0 comments on commit c610a49

Please sign in to comment.