001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and contributors. 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.Callable; 042import org.nuxeo.runtime.test.runner.FeaturesLoader.Direction; 043import org.nuxeo.runtime.test.runner.FeaturesLoader.Holder; 044 045import com.google.inject.Binder; 046import com.google.inject.Guice; 047import com.google.inject.Injector; 048import com.google.inject.Module; 049import com.google.inject.Stage; 050import com.google.inject.name.Names; 051 052/** 053 * A Test Case runner that can be extended through features and provide injection though Guice. 054 * 055 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 056 */ 057public class FeaturesRunner extends BlockJUnit4ClassRunner { 058 059 protected static final AnnotationScanner scanner = new AnnotationScanner(); 060 061 /** 062 * Guice injector. 063 */ 064 protected Injector injector; 065 066 protected final FeaturesLoader loader = new FeaturesLoader(this); 067 068 protected final TargetResourceLocator locator; 069 070 public static AnnotationScanner getScanner() { 071 return scanner; 072 } 073 074 public FeaturesRunner(Class<?> classToRun) throws InitializationError { 075 super(classToRun); 076 locator = new TargetResourceLocator(classToRun); 077 try { 078 loader.loadFeatures(getTargetTestClass()); 079 } catch (Throwable t) { 080 throw new InitializationError(Collections.singletonList(t)); 081 } 082 } 083 084 public Class<?> getTargetTestClass() { 085 return super.getTestClass().getJavaClass(); 086 } 087 088 public Path getTargetTestBasepath() { 089 return locator.getBasepath(); 090 } 091 092 public URL getTargetTestResource(String name) throws IOException { 093 return locator.getTargetTestResource(name); 094 } 095 096 public Iterable<RunnerFeature> getFeatures() { 097 return loader.features(); 098 } 099 100 /** 101 * @since 5.6 102 */ 103 public <T extends Annotation> T getConfig(Class<T> type) { 104 List<T> configs = new ArrayList<>(); 105 T annotation = scanner.getAnnotation(getTargetTestClass(), type); 106 if (annotation != null) { 107 configs.add(annotation); 108 } 109 loader.apply(Direction.BACKWARD, new Callable() { 110 @Override 111 public void call(Holder holder) throws Exception { 112 T hAnnotation = scanner.getAnnotation(holder.type, type); 113 if (hAnnotation != null) { 114 configs.add(hAnnotation); 115 } 116 } 117 }); 118 return Defaults.of(type, configs); 119 } 120 121 /** 122 * Get the annotation on the test method, if no annotation has been found, get the annotation from the test class 123 * (See {@link #getConfig(Class)}) 124 * 125 * @since 5.7 126 */ 127 public <T extends Annotation> T getConfig(FrameworkMethod method, Class<T> type) { 128 T config = method.getAnnotation(type); 129 if (config != null) { 130 return config; 131 } 132 // if not define, try to get the config of the class 133 return getConfig(type); 134 135 } 136 137 @Override 138 protected List<FrameworkMethod> computeTestMethods() { 139 List<FrameworkMethod> methods = super.computeTestMethods(); 140 // sort a copy 141 methods = new ArrayList<>(methods); 142 MethodSorter.sortMethodsUsingSourceOrder(methods); 143 return methods; 144 } 145 146 protected void initialize() throws Exception { 147 for (RunnerFeature each : getFeatures()) { 148 each.initialize(this); 149 } 150 } 151 152 protected void beforeRun() throws Exception { 153 loader.apply(Direction.FORWARD, new Callable() { 154 155 @Override 156 public void call(Holder holder) throws Exception { 157 holder.feature.beforeRun(FeaturesRunner.this); 158 } 159 }); 160 } 161 162 protected void beforeMethodRun(final FrameworkMethod method, final Object test) throws Exception { 163 loader.apply(Direction.FORWARD, new Callable() { 164 165 @Override 166 public void call(Holder holder) throws Exception { 167 holder.feature.beforeMethodRun(FeaturesRunner.this, method, test); 168 } 169 }); 170 } 171 172 protected void afterMethodRun(final FrameworkMethod method, final Object test) throws Exception { 173 loader.apply(Direction.FORWARD, new Callable() { 174 175 @Override 176 public void call(Holder holder) throws Exception { 177 holder.feature.afterMethodRun(FeaturesRunner.this, method, test); 178 } 179 }); 180 } 181 182 protected void afterRun() throws Exception { 183 loader.apply(Direction.BACKWARD, new Callable() { 184 185 @Override 186 public void call(Holder holder) throws Exception { 187 holder.feature.afterRun(FeaturesRunner.this); 188 } 189 }); 190 } 191 192 protected void start() throws Exception { 193 loader.apply(Direction.FORWARD, new Callable() { 194 195 @Override 196 public void call(Holder holder) throws Exception { 197 holder.feature.start(FeaturesRunner.this); 198 } 199 }); 200 } 201 202 protected void stop() throws Exception { 203 loader.apply(Direction.BACKWARD, new Callable() { 204 205 @Override 206 public void call(Holder holder) throws Exception { 207 holder.feature.stop(FeaturesRunner.this); 208 } 209 }); 210 } 211 212 protected void beforeSetup() throws Exception { 213 214 loader.apply(Direction.FORWARD, new Callable() { 215 216 @Override 217 public void call(Holder holder) throws Exception { 218 holder.feature.beforeSetup(FeaturesRunner.this); 219 } 220 221 }); 222 223 injector.injectMembers(underTest); 224 } 225 226 protected void afterTeardown() { 227 loader.apply(Direction.BACKWARD, new Callable() { 228 229 @Override 230 public void call(Holder holder) throws Exception { 231 holder.feature.afterTeardown(FeaturesRunner.this); 232 } 233 234 }); 235 } 236 237 public Injector getInjector() { 238 return injector; 239 } 240 241 protected Injector onInjector(final RunNotifier aNotifier) { 242 return Guice.createInjector(Stage.DEVELOPMENT, new Module() { 243 244 @Override 245 public void configure(Binder aBinder) { 246 aBinder.bind(FeaturesRunner.class).toInstance(FeaturesRunner.this); 247 aBinder.bind(RunNotifier.class).toInstance(aNotifier); 248 aBinder.bind(TargetResourceLocator.class).toInstance(locator); 249 } 250 251 }); 252 } 253 254 protected class BeforeClassStatement extends Statement { 255 protected final Statement next; 256 257 protected BeforeClassStatement(Statement aStatement) { 258 next = aStatement; 259 } 260 261 @Override 262 public void evaluate() throws Throwable { 263 initialize(); 264 start(); 265 beforeRun(); 266 injector = injector.createChildInjector(loader.onModule()); 267 try { 268 next.evaluate(); 269 } finally { 270 injector = injector.getParent(); 271 } 272 } 273 274 } 275 276 protected class AfterClassStatement extends Statement { 277 protected final Statement previous; 278 279 protected AfterClassStatement(Statement aStatement) { 280 previous = aStatement; 281 } 282 283 @Override 284 public void evaluate() throws Throwable { 285 previous.evaluate(); 286 try { 287 afterRun(); 288 } finally { 289 stop(); 290 } 291 } 292 } 293 294 @Override 295 protected Statement withAfterClasses(Statement statement) { 296 Statement actual = statement; 297 actual = super.withAfterClasses(actual); 298 actual = new AfterClassStatement(actual); 299 return actual; 300 } 301 302 @Override 303 protected Statement classBlock(final RunNotifier aNotifier) { 304 injector = onInjector(aNotifier); 305 return super.classBlock(aNotifier); 306 } 307 308 @Override 309 protected List<TestRule> classRules() { 310 final RulesFactory<ClassRule, TestRule> factory = new RulesFactory<>(ClassRule.class, TestRule.class); 311 312 factory.withRule(new TestRule() { 313 @Override 314 public Statement apply(Statement base, Description description) { 315 return new BeforeClassStatement(base); 316 } 317 }).withRules(super.classRules()); 318 loader.apply(Direction.FORWARD, new Callable() { 319 320 @Override 321 public void call(Holder holder) throws Exception { 322 factory.withRules(holder.testClass, null); 323 } 324 }); 325 326 return factory.build(); 327 } 328 329 protected class BeforeMethodRunStatement extends Statement { 330 331 protected final Statement next; 332 333 protected final FrameworkMethod method; 334 335 protected final Object target; 336 337 protected BeforeMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) { 338 method = aMethod; 339 target = aTarget; 340 next = aStatement; 341 } 342 343 @Override 344 public void evaluate() throws Throwable { 345 beforeMethodRun(method, target); 346 next.evaluate(); 347 } 348 349 } 350 351 protected class BeforeSetupStatement extends Statement { 352 353 protected final Statement next; 354 355 protected BeforeSetupStatement(Statement aStatement) { 356 next = aStatement; 357 } 358 359 @Override 360 public void evaluate() throws Throwable { 361 beforeSetup(); 362 next.evaluate(); 363 } 364 365 } 366 367 @Override 368 protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { 369 Statement actual = statement; 370 actual = new BeforeMethodRunStatement(method, target, actual); 371 actual = super.withBefores(method, target, actual); 372 actual = new BeforeSetupStatement(actual); 373 return actual; 374 } 375 376 protected class AfterMethodRunStatement extends Statement { 377 378 protected final Statement previous; 379 380 protected final FrameworkMethod method; 381 382 protected final Object target; 383 384 protected AfterMethodRunStatement(FrameworkMethod aMethod, Object aTarget, Statement aStatement) { 385 method = aMethod; 386 target = aTarget; 387 previous = aStatement; 388 } 389 390 @Override 391 public void evaluate() throws Throwable { 392 try { 393 previous.evaluate(); 394 } finally { 395 afterMethodRun(method, target); 396 } 397 } 398 399 } 400 401 protected class AfterTeardownStatement extends Statement { 402 403 protected final Statement previous; 404 405 protected AfterTeardownStatement(Statement aStatement) { 406 previous = aStatement; 407 } 408 409 @Override 410 public void evaluate() throws Throwable { 411 try { 412 previous.evaluate(); 413 } finally { 414 afterTeardown(); 415 } 416 } 417 418 } 419 420 @Override 421 protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { 422 Statement actual = statement; 423 actual = new AfterMethodRunStatement(method, target, actual); 424 actual = super.withAfters(method, target, actual); 425 actual = new AfterTeardownStatement(actual); 426 return actual; 427 } 428 429 @Override 430 protected List<TestRule> getTestRules(Object target) { 431 final RulesFactory<Rule, TestRule> factory = new RulesFactory<>(Rule.class, TestRule.class); 432 loader.apply(Direction.FORWARD, new Callable() { 433 434 @Override 435 public void call(Holder holder) throws Exception { 436 factory.withRules(holder.testClass, holder.feature); 437 } 438 439 }); 440 factory.withRules(getTestClass(), target); 441 return factory.build(); 442 } 443 444 @Override 445 protected List<MethodRule> rules(Object target) { 446 final RulesFactory<Rule, MethodRule> factory = new RulesFactory<>(Rule.class, MethodRule.class); 447 loader.apply(Direction.FORWARD, new Callable() { 448 449 @Override 450 public void call(Holder holder) throws Exception { 451 factory.withRules(holder.testClass, holder.feature); 452 } 453 454 }); 455 factory.withRules(getTestClass(), target); 456 return factory.build(); 457 } 458 459 @Override 460 protected Statement methodInvoker(FrameworkMethod method, Object test) { 461 final Statement actual = super.methodInvoker(method, test); 462 return new Statement() { 463 464 @Override 465 public void evaluate() throws Throwable { 466 injector.injectMembers(underTest); 467 actual.evaluate(); 468 } 469 470 }; 471 } 472 473 protected Object underTest; 474 475 @Override 476 public Object createTest() throws Exception { 477 underTest = super.createTest(); 478 loader.apply(Direction.FORWARD, new Callable() { 479 480 @Override 481 public void call(Holder holder) throws Exception { 482 holder.feature.testCreated(underTest); 483 } 484 485 }); 486 // TODO replace underTest member with a binding 487 // Class<?> testType = underTest.getClass(); 488 // injector.getInstance(Binder.class).bind(testType) 489 // .toInstance(testType.cast(underTest)); 490 return underTest; 491 } 492 493 @Override 494 protected void validateZeroArgConstructor(List<Throwable> errors) { 495 // Guice can inject constructors with parameters so we don't want this 496 // method to trigger an error 497 } 498 499 @Override 500 public String toString() { 501 return "FeaturesRunner [fTest=" + getTargetTestClass() + "]"; 502 } 503 504 protected class RulesFactory<A extends Annotation, R> { 505 506 protected Statement build(final Statement base, final String name) { 507 return new Statement() { 508 509 @Override 510 public void evaluate() throws Throwable { 511 injector = injector.createChildInjector(new Module() { 512 513 @SuppressWarnings({ "unchecked", "rawtypes" }) 514 @Override 515 public void configure(Binder binder) { 516 for (Object each : rules) { 517 binder.bind((Class) each.getClass()).annotatedWith(Names.named(name)).toInstance(each); 518 binder.requestInjection(each); 519 } 520 } 521 522 }); 523 524 try { 525 base.evaluate(); 526 } finally { 527 injector = injector.getParent(); 528 } 529 530 } 531 532 }; 533 534 } 535 536 protected class BindRule implements TestRule, MethodRule { 537 538 @Override 539 public Statement apply(Statement base, FrameworkMethod method, Object target) { 540 Statement statement = build(base, "method"); 541 for (Object each : rules) { 542 statement = ((MethodRule) each).apply(statement, method, target); 543 } 544 return statement; 545 } 546 547 @Override 548 public Statement apply(Statement base, Description description) { 549 if (rules.isEmpty()) { 550 return base; 551 } 552 Statement statement = build(base, "test"); 553 for (Object each : rules) { 554 statement = ((TestRule) each).apply(statement, description); 555 } 556 return statement; 557 } 558 559 } 560 561 protected final Class<A> annotationType; 562 563 protected final Class<R> ruleType; 564 565 protected ArrayList<R> rules = new ArrayList<>(); 566 567 protected RulesFactory(Class<A> anAnnotationType, Class<R> aRuleType) { 568 annotationType = anAnnotationType; 569 ruleType = aRuleType; 570 } 571 572 public RulesFactory<A, R> withRules(List<R> someRules) { 573 this.rules.addAll(someRules); 574 return this; 575 } 576 577 public RulesFactory<A, R> withRule(R aRule) { 578 injector.injectMembers(aRule); 579 rules.add(aRule); 580 return this; 581 } 582 583 public RulesFactory<A, R> withRules(TestClass aType, Object aTest) { 584 for (R each : aType.getAnnotatedFieldValues(aTest, annotationType, ruleType)) { 585 withRule(each); 586 } 587 588 for (FrameworkMethod each : aType.getAnnotatedMethods(annotationType)) { 589 if (ruleType.isAssignableFrom(each.getMethod().getReturnType())) { 590 withRule(onMethod(ruleType, each, aTest)); 591 } 592 } 593 return this; 594 } 595 596 public List<R> build() { 597 return Collections.singletonList(ruleType.cast(new BindRule())); 598 } 599 600 protected R onMethod(Class<R> aRuleType, FrameworkMethod aMethod, Object aTarget, Object... someParms) { 601 try { 602 return aRuleType.cast(aMethod.invokeExplosively(aTarget, someParms)); 603 } catch (Throwable cause) { 604 throw new RuntimeException("Errors in rules factory " + aMethod, cause); 605 } 606 } 607 608 } 609 610 public <T extends RunnerFeature> T getFeature(Class<T> aType) { 611 return loader.getFeature(aType); 612 } 613 614}