aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/runner/manipulation/Ordering.java
blob: 0d0ce93780e6bbc7ee826140f099943be5ecd4b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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);
    }
}