1 package org.junit.runners.parameterized; 2 3 import java.lang.annotation.Annotation; 4 import java.lang.reflect.Field; 5 import java.util.List; 6 7 import org.junit.internal.runners.statements.RunAfters; 8 import org.junit.internal.runners.statements.RunBefores; 9 import org.junit.runner.RunWith; 10 import org.junit.runner.notification.RunNotifier; 11 import org.junit.runners.BlockJUnit4ClassRunner; 12 import org.junit.runners.Parameterized; 13 import org.junit.runners.Parameterized.Parameter; 14 import org.junit.runners.model.FrameworkField; 15 import org.junit.runners.model.FrameworkMethod; 16 import org.junit.runners.model.InitializationError; 17 import org.junit.runners.model.Statement; 18 19 /** 20 * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be 21 * injected via constructor or into annotated fields. 22 */ 23 public class BlockJUnit4ClassRunnerWithParameters extends 24 BlockJUnit4ClassRunner { 25 private enum InjectionType { 26 CONSTRUCTOR, FIELD 27 } 28 29 private final Object[] parameters; 30 31 private final String name; 32 BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)33 public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) 34 throws InitializationError { 35 super(test.getTestClass()); 36 parameters = test.getParameters().toArray( 37 new Object[test.getParameters().size()]); 38 name = test.getName(); 39 } 40 41 @Override createTest()42 public Object createTest() throws Exception { 43 InjectionType injectionType = getInjectionType(); 44 switch (injectionType) { 45 case CONSTRUCTOR: 46 return createTestUsingConstructorInjection(); 47 case FIELD: 48 return createTestUsingFieldInjection(); 49 default: 50 throw new IllegalStateException("The injection type " 51 + injectionType + " is not supported."); 52 } 53 } 54 createTestUsingConstructorInjection()55 private Object createTestUsingConstructorInjection() throws Exception { 56 return getTestClass().getOnlyConstructor().newInstance(parameters); 57 } 58 createTestUsingFieldInjection()59 private Object createTestUsingFieldInjection() throws Exception { 60 List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); 61 if (annotatedFieldsByParameter.size() != parameters.length) { 62 throw new Exception( 63 "Wrong number of parameters and @Parameter fields." 64 + " @Parameter fields counted: " 65 + annotatedFieldsByParameter.size() 66 + ", available parameters: " + parameters.length 67 + "."); 68 } 69 Object testClassInstance = getTestClass().getJavaClass().newInstance(); 70 for (FrameworkField each : annotatedFieldsByParameter) { 71 Field field = each.getField(); 72 Parameter annotation = field.getAnnotation(Parameter.class); 73 int index = annotation.value(); 74 try { 75 field.set(testClassInstance, parameters[index]); 76 } catch (IllegalAccessException e) { 77 IllegalAccessException wrappedException = new IllegalAccessException( 78 "Cannot set parameter '" + field.getName() 79 + "'. Ensure that the field '" + field.getName() 80 + "' is public."); 81 wrappedException.initCause(e); 82 throw wrappedException; 83 } catch (IllegalArgumentException iare) { 84 throw new Exception(getTestClass().getName() 85 + ": Trying to set " + field.getName() 86 + " with the value " + parameters[index] 87 + " that is not the right type (" 88 + parameters[index].getClass().getSimpleName() 89 + " instead of " + field.getType().getSimpleName() 90 + ").", iare); 91 } 92 } 93 return testClassInstance; 94 } 95 96 @Override getName()97 protected String getName() { 98 return name; 99 } 100 101 @Override testName(FrameworkMethod method)102 protected String testName(FrameworkMethod method) { 103 return method.getName() + getName(); 104 } 105 106 @Override validateConstructor(List<Throwable> errors)107 protected void validateConstructor(List<Throwable> errors) { 108 validateOnlyOneConstructor(errors); 109 if (getInjectionType() != InjectionType.CONSTRUCTOR) { 110 validateZeroArgConstructor(errors); 111 } 112 } 113 114 @Override validateFields(List<Throwable> errors)115 protected void validateFields(List<Throwable> errors) { 116 super.validateFields(errors); 117 if (getInjectionType() == InjectionType.FIELD) { 118 List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); 119 int[] usedIndices = new int[annotatedFieldsByParameter.size()]; 120 for (FrameworkField each : annotatedFieldsByParameter) { 121 int index = each.getField().getAnnotation(Parameter.class) 122 .value(); 123 if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { 124 errors.add(new Exception("Invalid @Parameter value: " 125 + index + ". @Parameter fields counted: " 126 + annotatedFieldsByParameter.size() 127 + ". Please use an index between 0 and " 128 + (annotatedFieldsByParameter.size() - 1) + ".")); 129 } else { 130 usedIndices[index]++; 131 } 132 } 133 for (int index = 0; index < usedIndices.length; index++) { 134 int numberOfUse = usedIndices[index]; 135 if (numberOfUse == 0) { 136 errors.add(new Exception("@Parameter(" + index 137 + ") is never used.")); 138 } else if (numberOfUse > 1) { 139 errors.add(new Exception("@Parameter(" + index 140 + ") is used more than once (" + numberOfUse + ").")); 141 } 142 } 143 } 144 } 145 146 @Override classBlock(RunNotifier notifier)147 protected Statement classBlock(RunNotifier notifier) { 148 Statement statement = childrenInvoker(notifier); 149 statement = withBeforeParams(statement); 150 statement = withAfterParams(statement); 151 return statement; 152 } 153 withBeforeParams(Statement statement)154 private Statement withBeforeParams(Statement statement) { 155 List<FrameworkMethod> befores = getTestClass() 156 .getAnnotatedMethods(Parameterized.BeforeParam.class); 157 return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores); 158 } 159 160 private class RunBeforeParams extends RunBefores { RunBeforeParams(Statement next, List<FrameworkMethod> befores)161 RunBeforeParams(Statement next, List<FrameworkMethod> befores) { 162 super(next, befores, null); 163 } 164 165 @Override invokeMethod(FrameworkMethod method)166 protected void invokeMethod(FrameworkMethod method) throws Throwable { 167 int paramCount = method.getMethod().getParameterTypes().length; 168 method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); 169 } 170 } 171 withAfterParams(Statement statement)172 private Statement withAfterParams(Statement statement) { 173 List<FrameworkMethod> afters = getTestClass() 174 .getAnnotatedMethods(Parameterized.AfterParam.class); 175 return afters.isEmpty() ? statement : new RunAfterParams(statement, afters); 176 } 177 178 private class RunAfterParams extends RunAfters { RunAfterParams(Statement next, List<FrameworkMethod> afters)179 RunAfterParams(Statement next, List<FrameworkMethod> afters) { 180 super(next, afters, null); 181 } 182 183 @Override invokeMethod(FrameworkMethod method)184 protected void invokeMethod(FrameworkMethod method) throws Throwable { 185 int paramCount = method.getMethod().getParameterTypes().length; 186 method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); 187 } 188 } 189 190 @Override getRunnerAnnotations()191 protected Annotation[] getRunnerAnnotations() { 192 Annotation[] allAnnotations = super.getRunnerAnnotations(); 193 Annotation[] annotationsWithoutRunWith = new Annotation[allAnnotations.length - 1]; 194 int i = 0; 195 for (Annotation annotation: allAnnotations) { 196 if (!annotation.annotationType().equals(RunWith.class)) { 197 annotationsWithoutRunWith[i] = annotation; 198 ++i; 199 } 200 } 201 return annotationsWithoutRunWith; 202 } 203 getAnnotatedFieldsByParameter()204 private List<FrameworkField> getAnnotatedFieldsByParameter() { 205 return getTestClass().getAnnotatedFields(Parameter.class); 206 } 207 getInjectionType()208 private InjectionType getInjectionType() { 209 if (fieldsAreAnnotated()) { 210 return InjectionType.FIELD; 211 } else { 212 return InjectionType.CONSTRUCTOR; 213 } 214 } 215 fieldsAreAnnotated()216 private boolean fieldsAreAnnotated() { 217 return !getAnnotatedFieldsByParameter().isEmpty(); 218 } 219 } 220