001 package org.junit.runners.parameterized;
002
003 import java.lang.annotation.Annotation;
004 import java.lang.reflect.Field;
005 import java.util.List;
006
007 import org.junit.internal.runners.statements.RunAfters;
008 import org.junit.internal.runners.statements.RunBefores;
009 import org.junit.runner.RunWith;
010 import org.junit.runner.notification.RunNotifier;
011 import org.junit.runners.BlockJUnit4ClassRunner;
012 import org.junit.runners.Parameterized;
013 import org.junit.runners.Parameterized.Parameter;
014 import org.junit.runners.model.FrameworkField;
015 import org.junit.runners.model.FrameworkMethod;
016 import org.junit.runners.model.InitializationError;
017 import org.junit.runners.model.Statement;
018
019 /**
020 * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be
021 * injected via constructor or into annotated fields.
022 */
023 public class BlockJUnit4ClassRunnerWithParameters extends
024 BlockJUnit4ClassRunner {
025 private enum InjectionType {
026 CONSTRUCTOR, FIELD
027 }
028
029 private final Object[] parameters;
030
031 private final String name;
032
033 public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
034 throws InitializationError {
035 super(test.getTestClass());
036 parameters = test.getParameters().toArray(
037 new Object[test.getParameters().size()]);
038 name = test.getName();
039 }
040
041 @Override
042 public Object createTest() throws Exception {
043 InjectionType injectionType = getInjectionType();
044 switch (injectionType) {
045 case CONSTRUCTOR:
046 return createTestUsingConstructorInjection();
047 case FIELD:
048 return createTestUsingFieldInjection();
049 default:
050 throw new IllegalStateException("The injection type "
051 + injectionType + " is not supported.");
052 }
053 }
054
055 private Object createTestUsingConstructorInjection() throws Exception {
056 return getTestClass().getOnlyConstructor().newInstance(parameters);
057 }
058
059 private Object createTestUsingFieldInjection() throws Exception {
060 List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
061 if (annotatedFieldsByParameter.size() != parameters.length) {
062 throw new Exception(
063 "Wrong number of parameters and @Parameter fields."
064 + " @Parameter fields counted: "
065 + annotatedFieldsByParameter.size()
066 + ", available parameters: " + parameters.length
067 + ".");
068 }
069 Object testClassInstance = getTestClass().getJavaClass().newInstance();
070 for (FrameworkField each : annotatedFieldsByParameter) {
071 Field field = each.getField();
072 Parameter annotation = field.getAnnotation(Parameter.class);
073 int index = annotation.value();
074 try {
075 field.set(testClassInstance, parameters[index]);
076 } catch (IllegalAccessException e) {
077 IllegalAccessException wrappedException = new IllegalAccessException(
078 "Cannot set parameter '" + field.getName()
079 + "'. Ensure that the field '" + field.getName()
080 + "' is public.");
081 wrappedException.initCause(e);
082 throw wrappedException;
083 } catch (IllegalArgumentException iare) {
084 throw new Exception(getTestClass().getName()
085 + ": Trying to set " + field.getName()
086 + " with the value " + parameters[index]
087 + " that is not the right type ("
088 + parameters[index].getClass().getSimpleName()
089 + " instead of " + field.getType().getSimpleName()
090 + ").", iare);
091 }
092 }
093 return testClassInstance;
094 }
095
096 @Override
097 protected String getName() {
098 return name;
099 }
100
101 @Override
102 protected String testName(FrameworkMethod method) {
103 return method.getName() + getName();
104 }
105
106 @Override
107 protected void validateConstructor(List<Throwable> errors) {
108 validateOnlyOneConstructor(errors);
109 if (getInjectionType() != InjectionType.CONSTRUCTOR) {
110 validateZeroArgConstructor(errors);
111 }
112 }
113
114 @Override
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
147 protected Statement classBlock(RunNotifier notifier) {
148 Statement statement = childrenInvoker(notifier);
149 statement = withBeforeParams(statement);
150 statement = withAfterParams(statement);
151 return statement;
152 }
153
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 {
161 RunBeforeParams(Statement next, List<FrameworkMethod> befores) {
162 super(next, befores, null);
163 }
164
165 @Override
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
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 {
179 RunAfterParams(Statement next, List<FrameworkMethod> afters) {
180 super(next, afters, null);
181 }
182
183 @Override
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
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
204 private List<FrameworkField> getAnnotatedFieldsByParameter() {
205 return getTestClass().getAnnotatedFields(Parameter.class);
206 }
207
208 private InjectionType getInjectionType() {
209 if (fieldsAreAnnotated()) {
210 return InjectionType.FIELD;
211 } else {
212 return InjectionType.CONSTRUCTOR;
213 }
214 }
215
216 private boolean fieldsAreAnnotated() {
217 return !getAnnotatedFieldsByParameter().isEmpty();
218 }
219 }