001package org.junit.experimental.theories;
002
003import java.lang.reflect.Field;
004import java.lang.reflect.InvocationTargetException;
005import java.lang.reflect.Modifier;
006import java.util.ArrayList;
007import java.util.List;
008
009import org.junit.Assert;
010import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException;
011import org.junit.experimental.theories.internal.Assignments;
012import org.junit.experimental.theories.internal.ParameterizedAssertionError;
013import org.junit.internal.AssumptionViolatedException;
014import org.junit.runners.BlockJUnit4ClassRunner;
015import org.junit.runners.model.FrameworkMethod;
016import org.junit.runners.model.InitializationError;
017import org.junit.runners.model.Statement;
018import org.junit.runners.model.TestClass;
019
020public class Theories extends BlockJUnit4ClassRunner {
021    public Theories(Class<?> klass) throws InitializationError {
022        super(klass);
023    }
024
025    @Override
026    protected void collectInitializationErrors(List<Throwable> errors) {
027        super.collectInitializationErrors(errors);
028        validateDataPointFields(errors);
029    }
030
031    private void validateDataPointFields(List<Throwable> errors) {
032        Field[] fields = getTestClass().getJavaClass().getDeclaredFields();
033
034        for (Field field : fields) {
035            if (field.getAnnotation(DataPoint.class) == null) {
036                continue;
037            }
038            if (!Modifier.isStatic(field.getModifiers())) {
039                errors.add(new Error("DataPoint field " + field.getName() + " must be static"));
040            }
041            if (!Modifier.isPublic(field.getModifiers())) {
042                errors.add(new Error("DataPoint field " + field.getName() + " must be public"));
043            }
044        }
045    }
046
047    @Override
048    protected void validateConstructor(List<Throwable> errors) {
049        validateOnlyOneConstructor(errors);
050    }
051
052    @Override
053    protected void validateTestMethods(List<Throwable> errors) {
054        for (FrameworkMethod each : computeTestMethods()) {
055            if (each.getAnnotation(Theory.class) != null) {
056                each.validatePublicVoid(false, errors);
057                each.validateNoTypeParametersOnArgs(errors);
058            } else {
059                each.validatePublicVoidNoArg(false, errors);
060            }
061        }
062    }
063
064    @Override
065    protected List<FrameworkMethod> computeTestMethods() {
066        List<FrameworkMethod> testMethods = super.computeTestMethods();
067        List<FrameworkMethod> theoryMethods = getTestClass().getAnnotatedMethods(Theory.class);
068        testMethods.removeAll(theoryMethods);
069        testMethods.addAll(theoryMethods);
070        return testMethods;
071    }
072
073    @Override
074    public Statement methodBlock(final FrameworkMethod method) {
075        return new TheoryAnchor(method, getTestClass());
076    }
077
078    public static class TheoryAnchor extends Statement {
079        private int successes = 0;
080
081        private FrameworkMethod fTestMethod;
082        private TestClass fTestClass;
083
084        private List<AssumptionViolatedException> fInvalidParameters = new ArrayList<AssumptionViolatedException>();
085
086        public TheoryAnchor(FrameworkMethod method, TestClass testClass) {
087            fTestMethod = method;
088            fTestClass = testClass;
089        }
090
091        private TestClass getTestClass() {
092            return fTestClass;
093        }
094
095        @Override
096        public void evaluate() throws Throwable {
097            runWithAssignment(Assignments.allUnassigned(
098                    fTestMethod.getMethod(), getTestClass()));
099
100            if (successes == 0) {
101                Assert
102                        .fail("Never found parameters that satisfied method assumptions.  Violated assumptions: "
103                                + fInvalidParameters);
104            }
105        }
106
107        protected void runWithAssignment(Assignments parameterAssignment)
108                throws Throwable {
109            if (!parameterAssignment.isComplete()) {
110                runWithIncompleteAssignment(parameterAssignment);
111            } else {
112                runWithCompleteAssignment(parameterAssignment);
113            }
114        }
115
116        protected void runWithIncompleteAssignment(Assignments incomplete)
117                throws InstantiationException, IllegalAccessException,
118                Throwable {
119            for (PotentialAssignment source : incomplete
120                    .potentialsForNextUnassigned()) {
121                runWithAssignment(incomplete.assignNext(source));
122            }
123        }
124
125        protected void runWithCompleteAssignment(final Assignments complete)
126                throws InstantiationException, IllegalAccessException,
127                InvocationTargetException, NoSuchMethodException, Throwable {
128            new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
129                @Override
130                protected void collectInitializationErrors(
131                        List<Throwable> errors) {
132                    // do nothing
133                }
134
135                @Override
136                public Statement methodBlock(FrameworkMethod method) {
137                    final Statement statement = super.methodBlock(method);
138                    return new Statement() {
139                        @Override
140                        public void evaluate() throws Throwable {
141                            try {
142                                statement.evaluate();
143                                handleDataPointSuccess();
144                            } catch (AssumptionViolatedException e) {
145                                handleAssumptionViolation(e);
146                            } catch (Throwable e) {
147                                reportParameterizedError(e, complete
148                                        .getArgumentStrings(nullsOk()));
149                            }
150                        }
151
152                    };
153                }
154
155                @Override
156                protected Statement methodInvoker(FrameworkMethod method, Object test) {
157                    return methodCompletesWithParameters(method, complete, test);
158                }
159
160                @Override
161                public Object createTest() throws Exception {
162                    return getTestClass().getOnlyConstructor().newInstance(
163                            complete.getConstructorArguments(nullsOk()));
164                }
165            }.methodBlock(fTestMethod).evaluate();
166        }
167
168        private Statement methodCompletesWithParameters(
169                final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
170            return new Statement() {
171                @Override
172                public void evaluate() throws Throwable {
173                    try {
174                        final Object[] values = complete.getMethodArguments(
175                                nullsOk());
176                        method.invokeExplosively(freshInstance, values);
177                    } catch (CouldNotGenerateValueException e) {
178                        // ignore
179                    }
180                }
181            };
182        }
183
184        protected void handleAssumptionViolation(AssumptionViolatedException e) {
185            fInvalidParameters.add(e);
186        }
187
188        protected void reportParameterizedError(Throwable e, Object... params)
189                throws Throwable {
190            if (params.length == 0) {
191                throw e;
192            }
193            throw new ParameterizedAssertionError(e, fTestMethod.getName(),
194                    params);
195        }
196
197        private boolean nullsOk() {
198            Theory annotation = fTestMethod.getMethod().getAnnotation(
199                    Theory.class);
200            if (annotation == null) {
201                return false;
202            }
203            return annotation.nullsAccepted();
204        }
205
206        protected void handleDataPointSuccess() {
207            successes++;
208        }
209    }
210}