001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 */ 019package org.nuxeo.runtime.test.runner; 020 021import java.io.IOException; 022import java.lang.annotation.Annotation; 023import java.net.URL; 024import java.nio.file.Path; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.List; 028 029import org.junit.ClassRule; 030import org.junit.Rule; 031import org.junit.rules.MethodRule; 032import org.junit.rules.TestRule; 033import org.junit.runner.Description; 034import org.junit.runner.notification.RunNotifier; 035import org.junit.runners.BlockJUnit4ClassRunner; 036import org.junit.runners.model.FrameworkMethod; 037import org.junit.runners.model.InitializationError; 038import org.junit.runners.model.Statement; 039import org.junit.runners.model.TestClass; 040import org.nuxeo.runtime.test.TargetResourceLocator; 041import org.nuxeo.runtime.test.runner.FeaturesLoader.Direction; 042 043import com.google.inject.Binder; 044import com.google.inject.Guice; 045import com.google.inject.Injector; 046import com.google.inject.Module; 047import com.google.inject.Stage; 048import com.google.inject.name.Names; 049 050/** 051 * A Test Case runner that can be extended through features and provide injection though Guice. 052 * 053 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 054 */ 055public class FeaturesRunner extends BlockJUnit4ClassRunner { 056 057 protected static final AnnotationScanner scanner = new AnnotationScanner(); 058 059 /** 060 * Guice injector. 061 */ 062 protected Injector injector; 063 064 protected final FeaturesLoader loader = new FeaturesLoader(this); 065 066 protected final TargetResourceLocator locator; 067 068 public static AnnotationScanner getScanner() { 069 return scanner; 070 } 071 072 public FeaturesRunner(Class<?> classToRun) throws InitializationError { 073 super(classToRun); 074 locator = new TargetResourceLocator(classToRun); 075 try { 076 loader.loadFeatures(getTargetTestClass()); 077 } catch (Throwable t) { 078 throw new InitializationError(Collections.singletonList(t)); 079 } 080 } 081 082 public Class<?> getTargetTestClass() { 083 return super.getTestClass().getJavaClass(); 084 } 085 086 /** 087 * May return null if the test class was not yet instantiated 088 */ 089 public Object getTargetTestInstance() { 090 return underTest; 091 } 092 093 public Path getTargetTestBasepath() { 094 return locator.getBasepath(); 095 } 096 097 public URL getTargetTestResource(String name) throws IOException { 098 return locator.getTargetTestResource(name); 099 } 100 101 public Iterable<RunnerFeature> getFeatures() { 102 return loader.features(); 103 } 104 105 /** 106 * @since 5.6 107 */ 108 public <T extends Annotation> T getConfig(Class<T> type) { 109 List<T> configs = new ArrayList<>(); 110 T annotation = scanner.getAnnotation(getTargetTestClass(), type); 111 if (annotation != null) { 112 configs.add(annotation); 113 } 114 loader.apply(Direction.BACKWARD, holder -> { 115 T hAnnotation = scanner.getAnnotation(holder.type, type); 116 if (hAnnotation != null) { 117 configs.add(hAnnotation); 118 } 119 }); 120 return Defaults.of(type, configs); 121 } 122 123 /** 124 * Get the annotation on the test method, if no annotation has been found, get the annotation from the test class 125 * (See {@link #getConfig(Class)}) 126 * 127 * @since 5.7 128 */ 129 public <T extends Annotation> T getConfig(FrameworkMethod method, Class<T> type) { 130 T config = method.getAnnotation(type); 131 if (config != null) { 132 return config; 133 } 134 // if not define, try to get the config of the class 135 return getConfig(type); 136 137 } 138 139 @Override 140 protected List<FrameworkMethod> computeTestMethods() { 141 List<FrameworkMethod> methods = super.computeTestMethods(); 142 // sort a copy 143 methods = new ArrayList<>(methods); 144 MethodSorter.sortMethodsUsingSourceOrder(methods); 145 return methods; 146 } 147 148 protected void initialize() throws Exception { 149 for (RunnerFeature each : getFeatures()) { 150 each.initialize(this); 151 } 152 } 153 154 protected void beforeRun() throws Exception { 155 loader.apply(Direction.FORWARD, holder -> holder.feature.beforeRun(FeaturesRunner.this)); 156 } 157 158 protected void beforeMethodRun(final FrameworkMethod method, final Object test) throws Exception { 159 loader.apply(Direction.FORWARD, holder -> holder.feature.beforeMethodRun(FeaturesRunner.this, method, test)); 160 } 161 162 protected void afterMethodRun(final FrameworkMethod method, final Object test) throws Exception { 163 loader.apply(Direction.FORWARD, holder -> holder.feature.afterMethodRun(FeaturesRunner.this, method, test)); 164 } 165 166 protected void afterRun() throws Exception { 167 loader.apply(Direction.BACKWARD, holder -> holder.feature.afterRun(FeaturesRunner.this)); 168 } 169 170 protected void start() throws Exception { 171 loader.apply(Direction.FORWARD, holder -> holder.feature.start(FeaturesRunner.this)); 172 } 173 174 protected void stop() throws Exception { 175 loader.apply(Direction.BACKWARD, holder -> holder.feature.stop(FeaturesRunner.this)); 176 } 177 178 protected void beforeSetup() throws Exception { 179 loader.apply(Direction.FORWARD, holder -> holder.feature.beforeSetup(FeaturesRunner.this)); 180 injector.injectMembers(underTest); 181 } 182 183 protected void afterTeardown() { 184 loader.apply(Direction.BACKWARD, holder -> holder.feature.afterTeardown(FeaturesRunner.this)); 185 } 186 187 public Injector getInjector() { 188 return injector; 189 } 190 191 protected Injector onInjector(final RunNotifier aNotifier) { 192 return Guice.createInjector(Stage.DEVELOPMENT, new Module() { 193 194 @Override 195 public void configure(Binder aBinder) { 196 aBinder.bind(FeaturesRunner.class).toInstance(FeaturesRunner.this); 197 aBinder.bind(RunNotifier.class).toInstance(aNotifier); 198 aBinder.bind(TargetResourceLocator.class).toInstance(locator); 199 } 200 201 }); 202 } 203 204 protected class BeforeClassStatement extends Statement { 205 protected final Statement next; 206 207 protected BeforeClassStatement(Statement aStatement) { 208 next = aStatement; 209 } 210 211 @Override 212 public void evaluate() throws Throwable { 213 initialize(); 214 start(); 215 beforeRun(); 216 injector = injector.createChildInjector(loader.onModule()); 217 try { 218 next.evaluate(); 219 } finally { 220 injector = injector.getParent(); 221 } 222 } 223 224 } 225 226 protected class AfterClassStatement extends Statement { 227 protected final Statement previous; 228 229 protected AfterClassStatement(Statement aStatement) { 230 previous = aStatement; 231 } 232 233 @Override 234 public void evaluate() throws Throwable { 235 previous.evaluate(); 236 try { 237 afterRun(); 238 } finally { 239 stop(); 240 } 241 } 242 } 243 244 @Override 245 protected Statement withAfterClasses(Statement statement) { 246 Statement actual = statement; 247 actual = super.withAfterClasses(actual); 248 actual = new AfterClassStatement(actual); 249 return actual; 250 } 251 252 @Override 253 protected Statement classBlock(final RunNotifier aNotifier) { 254 injector = onInjector(aNotifier); 255 return super.classBlock(aNotifier); 256 } 257 258 @Override 259 protected List<TestRule> classRules() { 260 final RulesFactory<ClassRule, TestRule> factory = new RulesFactory<>(ClassRule.class, TestRule.class); 261 262 factory.withRule((base, description) -> new BeforeClassStatement(base)).withRules(super.classRules()); 263 loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, null)); 264 265 return factory.build(); 266 } 267 268 protected class BeforeMethodRunStatement extends Statement { 269 270 protected final Statement next; 271 272 protected final FrameworkMethod method; 273 274 protected final Object target; 275 276 protected BeforeMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) { 277 method = aMethod; 278 target = aTarget; 279 next = aStatement; 280 } 281 282 @Override 283 public void evaluate() throws Throwable { 284 beforeMethodRun(method, target); 285 next.evaluate(); 286 } 287 288 } 289 290 protected class BeforeSetupStatement extends Statement { 291 292 protected final Statement next; 293 294 protected BeforeSetupStatement(Statement aStatement) { 295 next = aStatement; 296 } 297 298 @Override 299 public void evaluate() throws Throwable { 300 beforeSetup(); 301 next.evaluate(); 302 } 303 304 } 305 306 @Override 307 protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { 308 Statement actual = statement; 309 actual = new BeforeMethodRunStatement(method, target, actual); 310 actual = super.withBefores(method, target, actual); 311 actual = new BeforeSetupStatement(actual); 312 return actual; 313 } 314 315 protected class AfterMethodRunStatement extends Statement { 316 317 protected final Statement previous; 318 319 protected final FrameworkMethod method; 320 321 protected final Object target; 322 323 protected AfterMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) { 324 method = aMethod; 325 target = aTarget; 326 previous = aStatement; 327 } 328 329 @Override 330 public void evaluate() throws Throwable { 331 try { 332 previous.evaluate(); 333 } finally { 334 afterMethodRun(method, target); 335 } 336 } 337 338 } 339 340 protected class AfterTeardownStatement extends Statement { 341 342 protected final Statement previous; 343 344 protected AfterTeardownStatement(Statement aStatement) { 345 previous = aStatement; 346 } 347 348 @Override 349 public void evaluate() throws Throwable { 350 try { 351 previous.evaluate(); 352 } finally { 353 afterTeardown(); 354 } 355 } 356 357 } 358 359 @Override 360 protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { 361 Statement actual = statement; 362 actual = new AfterMethodRunStatement(method, target, actual); 363 actual = super.withAfters(method, target, actual); 364 actual = new AfterTeardownStatement(actual); 365 return actual; 366 } 367 368 @Override 369 protected List<TestRule> getTestRules(Object target) { 370 final RulesFactory<Rule, TestRule> factory = new RulesFactory<>(Rule.class, TestRule.class); 371 loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, holder.feature)); 372 factory.withRules(getTestClass(), target); 373 return factory.build(); 374 } 375 376 @Override 377 protected List<MethodRule> rules(Object target) { 378 final RulesFactory<Rule, MethodRule> factory = new RulesFactory<>(Rule.class, MethodRule.class); 379 loader.apply(Direction.FORWARD, holder -> factory.withRules(holder.testClass, holder.feature)); 380 factory.withRules(getTestClass(), target); 381 return factory.build(); 382 } 383 384 protected Object underTest; 385 386 @Override 387 public Object createTest() throws Exception { 388 underTest = super.createTest(); 389 loader.apply(Direction.FORWARD, holder -> holder.feature.testCreated(underTest)); 390 // TODO replace underTest member with a binding 391 // Class<?> testType = underTest.getClass(); 392 // injector.getInstance(Binder.class).bind(testType) 393 // .toInstance(testType.cast(underTest)); 394 return underTest; 395 } 396 397 @Override 398 protected void validateZeroArgConstructor(List<Throwable> errors) { 399 // Guice can inject constructors with parameters so we don't want this 400 // method to trigger an error 401 } 402 403 @Override 404 public String toString() { 405 return "FeaturesRunner [fTest=" + getTargetTestClass() + "]"; 406 } 407 408 protected class RulesFactory<A extends Annotation, R> { 409 410 protected Statement build(final Statement base, final String name) { 411 return new Statement() { 412 413 @Override 414 public void evaluate() throws Throwable { 415 injector = injector.createChildInjector(new Module() { 416 417 @SuppressWarnings({ "unchecked", "rawtypes" }) 418 @Override 419 public void configure(Binder binder) { 420 for (Object each : rules) { 421 binder.bind((Class) each.getClass()).annotatedWith(Names.named(name)).toInstance(each); 422 binder.requestInjection(each); 423 } 424 } 425 426 }); 427 428 try { 429 base.evaluate(); 430 } finally { 431 injector = injector.getParent(); 432 } 433 434 } 435 436 }; 437 438 } 439 440 protected class BindRule implements TestRule, MethodRule { 441 442 @Override 443 public Statement apply(Statement base, FrameworkMethod method, Object target) { 444 Statement statement = build(base, "method"); 445 for (Object each : rules) { 446 statement = ((MethodRule) each).apply(statement, method, target); 447 } 448 return statement; 449 } 450 451 @Override 452 public Statement apply(Statement base, Description description) { 453 if (rules.isEmpty()) { 454 return base; 455 } 456 Statement statement = build(base, "test"); 457 for (Object each : rules) { 458 statement = ((TestRule) each).apply(statement, description); 459 } 460 return statement; 461 } 462 463 } 464 465 protected final Class<A> annotationType; 466 467 protected final Class<R> ruleType; 468 469 protected ArrayList<R> rules = new ArrayList<>(); 470 471 protected RulesFactory(Class<A> anAnnotationType, Class<R> aRuleType) { 472 annotationType = anAnnotationType; 473 ruleType = aRuleType; 474 } 475 476 public RulesFactory<A, R> withRules(List<R> someRules) { 477 this.rules.addAll(someRules); 478 return this; 479 } 480 481 public RulesFactory<A, R> withRule(R aRule) { 482 injector.injectMembers(aRule); 483 rules.add(aRule); 484 return this; 485 } 486 487 public RulesFactory<A, R> withRules(TestClass aType, Object aTest) { 488 for (R each : aType.getAnnotatedFieldValues(aTest, annotationType, ruleType)) { 489 withRule(each); 490 } 491 492 for (FrameworkMethod each : aType.getAnnotatedMethods(annotationType)) { 493 if (ruleType.isAssignableFrom(each.getMethod().getReturnType())) { 494 withRule(onMethod(ruleType, each, aTest)); 495 } 496 } 497 return this; 498 } 499 500 public List<R> build() { 501 return Collections.singletonList(ruleType.cast(new BindRule())); 502 } 503 504 protected R onMethod(Class<R> aRuleType, FrameworkMethod aMethod, Object aTarget, Object... someParms) { 505 try { 506 return aRuleType.cast(aMethod.invokeExplosively(aTarget, someParms)); 507 } catch (Throwable cause) { 508 throw new RuntimeException("Errors in rules factory " + aMethod, cause); 509 } 510 } 511 512 } 513 514 public <T extends RunnerFeature> T getFeature(Class<T> aType) { 515 return loader.getFeature(aType); 516 } 517 518}