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}