diff --git a/src/main/java/junit/framework/JUnit4TestAdapter.java b/src/main/java/junit/framework/JUnit4TestAdapter.java index cbb66dbb064c..4c9a9e3fcd78 100644 --- a/src/main/java/junit/framework/JUnit4TestAdapter.java +++ b/src/main/java/junit/framework/JUnit4TestAdapter.java @@ -81,6 +81,7 @@ public void filter(Filter filter) throws NoTestsRemainException { } public void sort(Sorter sorter) { - sorter.apply(fRunner); + if(sorter!=null) + sorter.apply(fRunner); } } \ No newline at end of file diff --git a/src/main/java/org/junit/SortWith.java b/src/main/java/org/junit/SortWith.java new file mode 100644 index 000000000000..0d2376412dee --- /dev/null +++ b/src/main/java/org/junit/SortWith.java @@ -0,0 +1,67 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Comparator; + +import org.junit.runners.Sorters; + +/** + * The SortWith Annotation that tells JUnit to execute test methods + * or test classes defined in a test suite in a order specified by users. + * It also supports randomness of test by designating a random sorting method + * to either a test class or a test suite. + * + *

+ * A sortable test class with its test methods executed in name ascending order looks like: + *

+ * 
+ * @RunWith(JUnit4.class)
+ * @SortWith(Sorters.NAME_ASCENDING)
+ * public class Example {
+ *    @Test
+ *    public void testMethod1() {
+ *       
+ *    }
+ *    
+ *    @Test
+ *    public void testMethod2() {
+ *     
+ *    }
+ * }
+ * 
+ *

+ * + *

+ * A sortable test suite with its test classes executed in name ascending order looks like: + *

+ * 
+ * @RunWith(Suite.class)
+ * @Suite.SuiteClasses({ ClassA.class, ClassB.class })
+ * @SortWith(Sorters.NAME_ASCENDING)
+ * public class Example {
+ * 
+ * }
+ * 
+ *

+ * + * The SortWith Annotation takes one input parameter, see {@link org.junit.runner.manipulation.Sorter}. + * The parameter declares which sorting method JUnit should adopt when + * starts running tests. If no sorting method is specified for the + * Annotation, then by default the default sorting method which compares + * hash codes of names of the two methods or names of two test classes is + * used. + * + * Be aware that using {@link org.junit.runner.Request#sortWith(Comparator)} to manually specify a sorting + * method for JUnit overrides all the declarative sorting methods specified through SortWith Annotation. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SortWith { + + Sorters value() default Sorters.DEFAULT; + +} diff --git a/src/main/java/org/junit/internal/requests/ClassRequest.java b/src/main/java/org/junit/internal/requests/ClassRequest.java index af136e71ebc1..5fb13789089b 100644 --- a/src/main/java/org/junit/internal/requests/ClassRequest.java +++ b/src/main/java/org/junit/internal/requests/ClassRequest.java @@ -3,6 +3,9 @@ import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.runner.Request; import org.junit.runner.Runner; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runners.ParentRunner; public class ClassRequest extends Request { private final Object fRunnerLock = new Object(); @@ -25,6 +28,8 @@ public Runner getRunner() { synchronized (fRunnerLock) { if (fRunner == null) { fRunner = new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass); + if(fRunner instanceof Sortable) + ((Sortable)fRunner).sort(Sorter.ANNOTATED_SORTER); } } } diff --git a/src/main/java/org/junit/runner/manipulation/Sorter.java b/src/main/java/org/junit/runner/manipulation/Sorter.java index 0bf5534acd34..fde483d25bef 100644 --- a/src/main/java/org/junit/runner/manipulation/Sorter.java +++ b/src/main/java/org/junit/runner/manipulation/Sorter.java @@ -5,8 +5,11 @@ import org.junit.runner.Description; /** - * A Sorter orders tests. In general you will not need - * to use a Sorter directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * A Sorter orders tests. In general you should specify the Sorter you + * want using SortWith Annotation. + * To use a Sorter directly, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * Be aware, as long as a Sorter is manually specified, all declarative sorting methods defined by + * SortWith Annotation will be overridden. * * @since 4.0 */ @@ -19,6 +22,59 @@ public int compare(Description o1, Description o2) { return 0; } }); + + public static final Sorter ANNOTATED_SORTER=null; + + /** + * DEFAULT sorting method. It compares hash codes of either names of two test methods or names of two test classes, + * depending on whether it is used for sorting execution order for test methods or sorting execution order for + * test classes defined in a test suite. + */ + public static final Sorter DEFAULT=new Sorter(new Comparator(){ + + public int compare(Description o1, Description o2) { + String[] items = getComparisonItems(o1,o2); + int hash1 = items[0].hashCode(); + int hash2 = items[1].hashCode(); + if (hash1 != hash2) { + return hash1 < hash2 ? -1 : 1; + } + return NAME_ASCENDING.compare(o1, o2); + } + + }); + + /** + * Sorting method based on name ascending order. It compares names of two test methods or names of two test classes, + * depending on whether it is used for sorting execution order for test methods or sorting execution order for + * test classes defined in a test suite. + */ + public static final Sorter NAME_ASCENDING = new Sorter(new Comparator(){ + + public int compare(Description o1, Description o2) { + String[] items = getComparisonItems(o1,o2); + final int comparison = items[0].compareTo(items[1]); + if (comparison != 0) { + return comparison; + } + return items[0].toString().compareTo(items[1].toString()); + } + + }); + + /** + * Random sorting method. It generates a random double value between 0 and 1 during runtime such that + * a set of test methods or a set of test classes will always run in a random order. + */ + public static final Sorter RANDOM = new Sorter(new Comparator(){ + + public int compare(Description o1, Description o2) { + return Math.random() - 0.5 >0 ? 1 : -1; + } + + }); + + //public static final Sorter JVM private final Comparator fComparator; @@ -45,4 +101,15 @@ public void apply(Object object) { public int compare(Description o1, Description o2) { return fComparator.compare(o1, o2); } + + private final static String[] getComparisonItems(Description o1,Description o2){ + if(o1.isSuite()){ + final String[] items={o1.getClassName(),o2.getClassName()}; + return items; + }else{ + final String[] items={o1.getMethodName(),o2.getMethodName()}; + return items; + } + } + } diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java index 8d9bf8ac7939..0c7b8a5a1dc1 100755 --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -17,6 +17,7 @@ import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Rule; +import org.junit.SortWith; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.statements.RunAfters; @@ -384,13 +385,25 @@ public void filter(Filter filter) throws NoTestsRemainException { } public void sort(Sorter sorter) { - synchronized (fChildrenLock) { - for (T each : getFilteredChildren()) { - sorter.apply(each); - } + if(sorter==Sorter.ANNOTATED_SORTER){ List sortedChildren = new ArrayList(getFilteredChildren()); - Collections.sort(sortedChildren, comparator(sorter)); + Sorter annotatedSorter=getSorter(); + if(annotatedSorter!=null) + Collections.sort(sortedChildren, comparator(annotatedSorter)); fFilteredChildren = Collections.unmodifiableCollection(sortedChildren); + for (T each : getFilteredChildren()) { + if(each instanceof Sortable) + ((Sortable)each).sort(null); + } + }else{ + synchronized (fChildrenLock) { + for (T each : getFilteredChildren()) { + sorter.apply(each); + } + List sortedChildren = new ArrayList(getFilteredChildren()); + Collections.sort(sortedChildren, comparator(sorter)); + fFilteredChildren = Collections.unmodifiableCollection(sortedChildren); + } } } @@ -436,4 +449,14 @@ public int compare(T o1, T o2) { public void setScheduler(RunnerScheduler scheduler) { this.fScheduler = scheduler; } + + private Sorter getSorter(){ + final TestClass clazz = getTestClass(); + if(clazz==null) + return null; + SortWith sortMethod=clazz.getJavaClass().getAnnotation(SortWith.class); + if(sortMethod==null) + return null; + return sortMethod.value().getSorter(); + } } diff --git a/src/main/java/org/junit/runners/Sorters.java b/src/main/java/org/junit/runners/Sorters.java new file mode 100644 index 000000000000..1f3b36557a25 --- /dev/null +++ b/src/main/java/org/junit/runners/Sorters.java @@ -0,0 +1,44 @@ +package org.junit.runners; + +import org.junit.runner.manipulation.Sorter; + + +/** + * Sorters enumerator used for defining sorting + * method for {@link SortWith} + * + */ +public enum Sorters { + + /** + * default sorting method, using hash code + * of a method name o a class name to compare + */ + DEFAULT(Sorter.DEFAULT), + + /** + * random sorting method. + */ + RANDOM(Sorter.RANDOM), + + /** + * sorting method based on name ascending order + */ + NAME_ASCENDING(Sorter.NAME_ASCENDING), + + /** + * JVM sorting method + */ + JVM(null); + + private final Sorter sorter; + + private Sorters(Sorter sorter){ + this.sorter=sorter; + } + + public Sorter getSorter(){ + return sorter; + } + +} diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java index e02ec9bb87db..6acfdc6af084 100755 --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -61,7 +61,7 @@ public TestClass(Class klass) { protected void scanAnnotatedMembers(Map, List> methodsForAnnotations, Map, List> fieldsForAnnotations) { for (Class eachClass : getSuperClasses(fClass)) { - for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) { + for (Method eachMethod : eachClass.getDeclaredMethods()) { addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations); } // ensuring fields are sorted to make sure that entries are inserted diff --git a/src/test/java/org/junit/tests/manipulation/ClassSorterTest.java b/src/test/java/org/junit/tests/manipulation/ClassSorterTest.java new file mode 100644 index 000000000000..c0d27e845826 --- /dev/null +++ b/src/test/java/org/junit/tests/manipulation/ClassSorterTest.java @@ -0,0 +1,317 @@ +package org.junit.tests.manipulation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.junit.SortWith; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.Description; +import org.junit.runner.JUnitCore; +import org.junit.runner.Request; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.runners.Sorters; +import org.junit.runners.Suite; + +@RunWith(Enclosed.class) +public class ClassSorterTest { + + private static Comparator nameDescending() { + return new Comparator() { + public int compare(Description o1, Description o2) { + return o2.getClassName().compareTo(o1.getClassName()); + } + }; + } + + public static class SortClassInSuiteByName { + + private static String str = ""; + + @RunWith(JUnit4.class) + public static class ClassE { + + @Test + public void testE() { + str += "e"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassB { + + @Test + public void testB() { + str += "b"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassC { + + @Test + public void testC() { + str += "c"; + } + + } + + @RunWith(Suite.class) + @Suite.SuiteClasses({ ClassE.class, ClassB.class, ClassC.class }) + @SortWith(Sorters.NAME_ASCENDING) + public static class ClassSuiteSortByName { + + } + + @Test + public void testSortClassByName() { + Request request = Request.aClass(ClassSuiteSortByName.class); + new JUnitCore().run(request); + assertEquals("bce", str); + } + + } + + public static class SortClassInSuiteByDefault { + + private static List list = new ArrayList(); + + @RunWith(JUnit4.class) + public static class Fantastic { + + @Test + public void testAdd() { + list.add(Fantastic.class.getName()); + } + + } + + @RunWith(JUnit4.class) + public static class Fabulent { + + @Test + public void testAdd() { + list.add(Fabulent.class.getName()); + } + + } + + @RunWith(JUnit4.class) + public static class Splendid { + + @Test + public void testAdd() { + list.add(Splendid.class.getName()); + } + + } + + @RunWith(Suite.class) + @Suite.SuiteClasses({ Fantastic.class, Fabulent.class, Splendid.class }) + @SortWith(Sorters.DEFAULT) + public static class ClassSuiteSortByDefault { + + } + + @Test + public void testSortClassByDefault() { + Request request = Request.aClass(ClassSuiteSortByDefault.class); + new JUnitCore().run(request); + String[] strs = { Fantastic.class.getName(), + Fabulent.class.getName(), Splendid.class.getName() }; + Arrays.sort(strs, new Comparator() { + + public int compare(String o1, String o2) { + int item1 = o1.hashCode(); + int item2 = o2.hashCode(); + if (item1 != item2) { + return item1 < item2 ? -1 : 1; + } + return 0; + } + + }); + String[] results = new String[3]; + assertEquals(strs, SortClassInSuiteByDefault.list.toArray(results)); + } + + } + + public static class SortClassInSuiteByRandom { + + public static String str = ""; + + @RunWith(JUnit4.class) + public static class ClassRA { + + @Test + public void testAdd() { + str += "RA"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassRB { + + @Test + public void testAdd() { + str += "RB"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassRC { + + @Test + public void testAdd() { + str += "RC"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassRD { + + @Test + public void testAdd() { + str += "RD"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassRE { + + @Test + public void testAdd() { + str += "RE"; + } + + } + + @RunWith(Suite.class) + @Suite.SuiteClasses({ ClassRA.class, ClassRB.class, ClassRC.class, + ClassRD.class, ClassRE.class }) + @SortWith(Sorters.RANDOM) + public static class ClassSuiteSortByRandom { + + } + + @Test + public void testSortClassByRandom() { + String lastExecutionOrder = null; + int i = 0; + for (; i < 10; i++, lastExecutionOrder = SortClassInSuiteByRandom.str, lastExecutionOrder = SortClassInSuiteByRandom.str = "") { + Request request = Request.aClass(ClassSuiteSortByRandom.class); + new JUnitCore().run(request); + if (lastExecutionOrder == null) { + lastExecutionOrder = SortClassInSuiteByRandom.str; + continue; + } + if (!lastExecutionOrder.equals(SortClassInSuiteByRandom.str)) + break; + } + assertNotEquals(10, i); + } + } + + public static class SortClassInSuiteByJVM { + + public static String str = ""; + + @RunWith(JUnit4.class) + public static class ClassJVMA { + + @Test + public void testAdd() { + str += "A"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassJVMB { + + @Test + public void testAdd() { + str += "B"; + } + + } + + @RunWith(Suite.class) + @Suite.SuiteClasses({ ClassJVMA.class, ClassJVMB.class }) + @SortWith(Sorters.JVM) + public static class ClassSuiteSortByJVM { + + } + + @Test + public void testSortClassByJVM() { + Request request = Request.aClass(ClassSuiteSortByJVM.class); + new JUnitCore().run(request); + String test1 = SortClassInSuiteByJVM.str; + SortClassInSuiteByJVM.str = ""; + request = Request.aClass(ClassSuiteSortByJVM.class); + new JUnitCore().run(request); + String test2 = SortClassInSuiteByJVM.str; + assertEquals(test1, test2); + } + + } + + public static class OverrideSortingMethodInSuite { + + public static String str = ""; + + @RunWith(JUnit4.class) + public static class ClassA { + + @Test + public void testAdd() { + str += "a"; + } + + } + + @RunWith(JUnit4.class) + public static class ClassB { + + @Test + public void testAdd() { + str += "b"; + } + + } + + @RunWith(Suite.class) + @Suite.SuiteClasses({ ClassA.class, ClassB.class }) + @SortWith(Sorters.NAME_ASCENDING) + public static class ClassSuiteSortByNameAscending { + + } + + @Test + public void testOverrideClassOrder() { + Request request = Request.aClass(ClassSuiteSortByNameAscending.class); + new JUnitCore().run(request); + assertEquals("ab",OverrideSortingMethodInSuite.str); + OverrideSortingMethodInSuite.str=""; + request = Request.aClass(ClassSuiteSortByNameAscending.class).sortWith(nameDescending()); + new JUnitCore().run(request); + assertEquals("ba",OverrideSortingMethodInSuite.str); + } + + } + +} diff --git a/src/test/java/org/junit/tests/manipulation/MethodSorterTest.java b/src/test/java/org/junit/tests/manipulation/MethodSorterTest.java new file mode 100644 index 000000000000..4a346f41259b --- /dev/null +++ b/src/test/java/org/junit/tests/manipulation/MethodSorterTest.java @@ -0,0 +1,262 @@ +package org.junit.tests.manipulation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.SortWith; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.Description; +import org.junit.runner.JUnitCore; +import org.junit.runner.Request; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.runners.Sorters; + +@RunWith(Enclosed.class) +public class MethodSorterTest { + + private static Comparator nameDescending() { + return new Comparator() { + public int compare(Description o1, Description o2) { + return o2.getDisplayName().compareTo(o1.getDisplayName()); + } + }; + } + + public static class TestSortByName { + + @RunWith(JUnit4.class) + @SortWith(Sorters.NAME_ASCENDING) + public static class SortByName { + + public static String str = ""; + + @Test + public void testA() { + str += "a"; + } + + @Test + public void testB() { + str += "b"; + } + + @Test + public void testC() { + str += "c"; + } + + @Test + public void testD() { + str += "d"; + } + + @Test + public void testE() { + str += "e"; + } + } + + @Test + public void testNameAscendingOrder() { + Request request = Request.aClass(SortByName.class); + new JUnitCore().run(request); + assertEquals("abcde", SortByName.str); + } + } + + public static class TestSortByDefault { + + @RunWith(JUnit4.class) + @SortWith + public static class SortByDefault { + + public static List list = new ArrayList(); + + @Test + public void fun() { + list.add("fun"); + } + + @Test + public void ecstasy() { + list.add("ecstasy"); + } + + @Test + public void happy() { + list.add("happy"); + } + + @Test + public void meow() { + list.add("meow"); + } + + @Test + public void halirious() { + list.add("halirious"); + } + } + + @Test + public void testDefaultOrder() { + Request request = Request.aClass(SortByDefault.class); + new JUnitCore().run(request); + String[] strs = { "fun", "ecstasy", "happy", "meow" ,"halirious"}; + Arrays.sort(strs, new Comparator() { + + public int compare(String o1, String o2) { + int item1 = o1.hashCode(); + int item2 = o2.hashCode(); + if (item1 != item2) { + return item1 < item2 ? -1 : 1; + } + return 0; + } + + }); + String[] results = new String[4]; + assertEquals(strs, SortByDefault.list.toArray(results)); + } + } + + public static class TestSortByRandom { + + @RunWith(JUnit4.class) + @SortWith(Sorters.RANDOM) + public static class SortByRandom{ + + public static String str=""; + + @Test + public void testA(){ + str += "a"; + } + + @Test + public void testB(){ + str += "b"; + } + + @Test + public void testC(){ + str += "c"; + } + + @Test + public void testD(){ + str += "d"; + } + + @Test + public void testE(){ + str += "e"; + } + + @Test + public void testF(){ + str += "f"; + } + + } + + @Test + public void testRandomOrder() { + String lastExecutionOrder=null; + int i=0; + for(;i<10;i++,lastExecutionOrder=SortByRandom.str,SortByRandom.str=""){ + Request request = Request.aClass(SortByRandom.class); + new JUnitCore().run(request); + if(lastExecutionOrder==null){ + lastExecutionOrder=SortByRandom.str; + continue; + } + if(!lastExecutionOrder.equals(SortByRandom.str)) + break; + } + assertNotEquals(10,i); + } + + } + + public static class TestSortByJVM{ + + @RunWith(JUnit4.class) + @SortWith(Sorters.JVM) + public static class SortByJVM{ + + public static String str=""; + + @Test + public void testA(){ + str+="a"; + } + + @Test + public void testB(){ + str+="b"; + } + + } + + @Test + public void testJVMOrder(){ + Request request = Request.aClass(SortByJVM.class); + new JUnitCore().run(request); + String test1=SortByJVM.str; + SortByJVM.str=""; + request = Request.aClass(SortByJVM.class); + new JUnitCore().run(request); + String test2=SortByJVM.str; + assertEquals(test1,test2); + } + } + + public static class TestOverrideSorting{ + + @RunWith(JUnit4.class) + @SortWith(Sorters.NAME_ASCENDING) + public static class SortByNameAscending{ + + public static String str=""; + + @Test + public void testA(){ + str+="a"; + } + + @Test + public void testB(){ + str+="b"; + } + + @Test + public void testC(){ + str+="c"; + } + + } + + @Test + public void testOverideSorting(){ + Request ascending=Request.aClass(SortByNameAscending.class); + new JUnitCore().run(ascending); + assertEquals("abc",SortByNameAscending.str); + SortByNameAscending.str=""; + Request descending = Request.aClass(SortByNameAscending.class).sortWith(nameDescending()); + new JUnitCore().run(descending); + assertEquals("cba",SortByNameAscending.str); + } + + } + +} diff --git a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java index 4ce0140fe1c9..14dc47e53f4e 100644 --- a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java +++ b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java @@ -22,10 +22,17 @@ import org.junit.runners.model.RunnerScheduler; import org.junit.tests.experimental.rules.RuleFieldValidatorTest.TestWithNonStaticClassRule; import org.junit.tests.experimental.rules.RuleFieldValidatorTest.TestWithProtectedClassRule; +import org.junit.SortWith; public class ParentRunnerTest { public static String log = ""; + /* + * Adding SortWith here because MethodSorter is no longer used when creating a test case. + * So if no sorting method is specified, the test methods will be executed in JVM order, + * which has chance to produce randomness to fail this test case. + */ + @SortWith public static class FruitTest { @Test public void apple() { @@ -41,7 +48,8 @@ public void apple() { @Test public void useChildHarvester() throws InitializationError { log = ""; - ParentRunner runner = new BlockJUnit4ClassRunner(FruitTest.class); + Request request = Request.aClass(FruitTest.class); + ParentRunner runner = (ParentRunner)request.getRunner(); runner.setScheduler(new RunnerScheduler() { public void schedule(Runnable childStatement) { log += "before ";