1package com.nordstrom.automation.testng;2import java.lang.annotation.Annotation;3import java.lang.reflect.Constructor;4import java.lang.reflect.InvocationTargetException;5import java.lang.reflect.Method;6import java.util.ArrayList;7import java.util.Collections;8import java.util.HashSet;9import java.util.List;10import java.util.Objects;11import java.util.ServiceLoader;12import java.util.Set;13import org.testng.IAnnotationTransformer;14import org.testng.IClassListener;15import org.testng.IConfigurationListener;16import org.testng.IExecutionListener;17import org.testng.IInvokedMethod;18import org.testng.IInvokedMethodListener;19import org.testng.IMethodInstance;20import org.testng.IMethodInterceptor;21import org.testng.ISuite;22import org.testng.ISuiteListener;23import org.testng.ITestClass;24import org.testng.ITestContext;25import org.testng.ITestListener;26import org.testng.ITestNGListener;27import org.testng.ITestResult;28import org.testng.annotations.ITestAnnotation;29import com.google.common.base.Optional;30import com.google.common.collect.Lists;31/**32 * This TestNG listener enables the addition of other listeners at runtime and guarantees the order in which they're33 * invoked. This is similar in behavior to a JUnit rule chain.34 */35public abstract class AbstractListenerChain implements IAnnotationTransformer, IExecutionListener, ISuiteListener,36 IConfigurationListener, IInvokedMethodListener, ITestListener, IMethodInterceptor, IClassListener {37 38 private Set<Class<?>> markedClasses = Collections.synchronizedSet(new HashSet<Class<?>>());39 private Set<Class<? extends ITestNGListener>> listenerSet = 40 Collections.synchronizedSet(new HashSet<Class<? extends ITestNGListener>>());41 42 protected List<ITestNGListener> listeners;43 protected List<IAnnotationTransformer> annotationXformers;44 protected List<IExecutionListener> executionListeners;45 protected List<ISuiteListener> suiteListeners;46 protected List<IConfigurationListener> configListeners;47 protected List<IInvokedMethodListener> methodListeners;48 protected List<ITestListener> testListeners;49 protected List<IMethodInterceptor> methodInterceptors;50 protected List<IClassListener> classListeners;51 52 private static final String LISTENER_CHAIN = "ListenerChain";53 54 public AbstractListenerChain() {55 initialize();56 for (LinkedListener listener : ServiceLoader.load(LinkedListener.class)) {57 attachListener(null, listener);58 }59 }60 /**61 * [IAnnotationTransformer]62 * This method will be invoked by TestNG to give you a chance to modify a TestNG annotation read from your test63 * classes. You can change the values you need by calling any of the setters on the ITest interface. Note that64 * only one of the three parameters testClass, testCtor and testMethod will be non-null.65 * 66 * @param annotation The annotation that was read from your test class.67 * @param testClass If the annotation was found on a class, this parameter represents this class (null otherwise).68 * @param testCtor If the annotation was found on a constructor, this parameter represents this constructor (null69 * otherwise).70 * @param testMethod If the annotation was found on a method, this parameter represents this method (null71 * otherwise).72 */73 @Override74 @SuppressWarnings("rawtypes")75 public void transform(ITestAnnotation annotation, Class testClass, Constructor testCtor, Method testMethod) {76 attachListeners(testClass, testCtor, testMethod);77 78 synchronized(annotationXformers) {79 for (IAnnotationTransformer annotationXformer : annotationXformers) {80 annotationXformer.transform(annotation, testClass, testCtor, testMethod);81 }82 }83 }84 /**85 * [IExecutionListener]86 * Invoked before the TestNG run starts.87 */88 @Override89 public void onExecutionStart() {90 synchronized(executionListeners) {91 for (IExecutionListener executionListener : executionListeners) {92 executionListener.onExecutionStart();93 }94 }95 }96 /**97 * [IExecutionListener]98 * Invoked once all the suites have been run.99 */100 @Override101 public void onExecutionFinish() {102 synchronized(executionListeners) {103 for (IExecutionListener executionListener : executionListeners) {104 executionListener.onExecutionFinish();105 }106 }107 }108 109 /**110 * [ISuiteListener]111 * This method is invoked before the SuiteRunner starts.112 * 113 * @param suite current test suite114 */115 @Override116 public void onStart(ISuite suite) {117 suite.setAttribute(LISTENER_CHAIN, this);118 119 synchronized(suiteListeners) {120 for (ISuiteListener suiteListener : Lists.reverse(suiteListeners)) {121 suiteListener.onStart(suite);122 }123 }124 }125 /**126 * [ISuiteListener]127 * This method is invoked after the SuiteRunner has run all128 * the test suites.129 * 130 * @param suite current test suite131 */132 @Override133 public void onFinish(ISuite suite) {134 synchronized(suiteListeners) {135 for (ISuiteListener suiteListener : suiteListeners) {136 suiteListener.onFinish(suite);137 }138 }139 }140 /**141 * [IConfigurationListener]142 * Invoked whenever a configuration method succeeded.143 * 144 * @param itr test result object for the associated configuration method145 */146 @Override147 public void onConfigurationSuccess(ITestResult itr) {148 synchronized(configListeners) {149 for (IConfigurationListener configListener : configListeners) {150 configListener.onConfigurationSuccess(itr);151 }152 }153 }154 /**155 * [IConfigurationListener]156 * Invoked whenever a configuration method failed.157 * 158 * @param itr test result object for the associated configuration method159 */160 @Override161 public void onConfigurationFailure(ITestResult itr) {162 synchronized(configListeners) {163 for (IConfigurationListener configListener : configListeners) {164 configListener.onConfigurationFailure(itr);165 }166 }167 }168 /**169 * [IConfigurationListener]170 * Invoked whenever a configuration method was skipped.171 * 172 * @param itr test result object for the associated configuration method173 */174 @Override175 public void onConfigurationSkip(ITestResult itr) {176 synchronized(configListeners) {177 for (IConfigurationListener configListener : configListeners) {178 configListener.onConfigurationSkip(itr);179 }180 }181 }182 /**183 * [IInvokedMethodListener]184 * Invoked before each test or configuration method is invoked by TestNG185 * 186 * @param method TestNG representation of the method that's about to be invoked187 * @param testResult test result object for the method that's about to be invoked188 */189 @Override190 public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {191 // NOTE: This method will never be called192 }193 /**194 * [IInvokedMethodListener]195 * Invoked after each test or configuration method is invoked by TestNG196 * 197 * @param method TestNG representation of the method that's just been invoked198 * @param testResult test result object for the method that's just been invoked199 */200 @Override201 public void afterInvocation(IInvokedMethod method, ITestResult testResult) {202 // NOTE: This method will never be called203 }204 205 /**206 * [IInvokedMethodListener(2)]207 * Invoked before each test or configuration method is invoked by TestNG208 * 209 * @param method TestNG representation of the method that's about to be invoked210 * @param testResult test result object for the method that's about to be invoked211 * @param context test context212 */213 // @Override omitted to avoid interface conflict214 public void beforeInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {215 synchronized(methodListeners) {216 for (IInvokedMethodListener methodListener : Lists.reverse(methodListeners)) {217 methodListener.beforeInvocation(method, testResult);218 }219 }220 }221 /**222 * [IInvokedMethodListener(2)]223 * Invoked after each test or configuration method is invoked by TestNG224 * 225 * @param method TestNG representation of the method that's just been invoked226 * @param testResult test result object for the method that's just been invoked227 * @param context text context228 */229 // @Override omitted to avoid interface conflict230 public void afterInvocation(IInvokedMethod method, ITestResult testResult, ITestContext context) {231 synchronized(methodListeners) {232 for (IInvokedMethodListener methodListener : methodListeners) {233 methodListener.afterInvocation(method, testResult);234 }235 }236 }237 /**238 * [ITestListener]239 * Invoked each time before a test will be invoked.240 * The {@code ITestResult} is only partially filled with the references to241 * class, method, start millis and status.242 *243 * @param result the partially filled {@code ITestResult}244 * @see ITestResult#STARTED245 */246 @Override247 public void onTestStart(ITestResult result) {248 synchronized(testListeners) {249 for (ITestListener testListener : Lists.reverse(testListeners)) {250 testListener.onTestStart(result);251 }252 }253 }254 /**255 * [ITestListener]256 * Invoked each time a test succeeds.257 *258 * @param result {@code ITestResult} containing information about the run test259 * @see ITestResult#SUCCESS260 */261 @Override262 public void onTestSuccess(ITestResult result) {263 synchronized (testListeners) {264 for (ITestListener testListener : testListeners) {265 testListener.onTestSuccess(result);266 }267 }268 }269 /**270 * [ITestListener]271 * Invoked each time a test fails.272 *273 * @param result {@code ITestResult} containing information about the run test274 * @see ITestResult#FAILURE275 */276 @Override277 public void onTestFailure(ITestResult result) {278 synchronized (testListeners) {279 for (ITestListener testListener : testListeners) {280 testListener.onTestFailure(result);281 }282 }283 }284 /**285 * [ITestListener]286 * Invoked each time a test is skipped.287 *288 * @param result {@code ITestResult} containing information about the run test289 * @see ITestResult#SKIP290 */291 @Override292 public void onTestSkipped(ITestResult result) {293 synchronized (testListeners) {294 for (ITestListener testListener : testListeners) {295 testListener.onTestSkipped(result);296 }297 }298 }299 /**300 * [ITestListener]301 * Invoked each time a method fails but has been annotated with302 * successPercentage and this failure still keeps it within the303 * success percentage requested.304 *305 * @param result {@code ITestResult} containing information about the run test306 * @see ITestResult#SUCCESS_PERCENTAGE_FAILURE307 */308 @Override309 public void onTestFailedButWithinSuccessPercentage(ITestResult result) {310 synchronized (testListeners) {311 for (ITestListener testListener : testListeners) {312 testListener.onTestFailedButWithinSuccessPercentage(result);313 }314 }315 }316 /**317 * [ITestListener]318 * Invoked after the test class is instantiated and before319 * any configuration method is called.320 * 321 * @param context context for the test run322 */323 @Override324 public void onStart(ITestContext context) {325 synchronized (testListeners) {326 for (ITestListener testListener : Lists.reverse(testListeners)) {327 testListener.onStart(context);328 }329 }330 }331 /**332 * [ITestListener]333 * Invoked after all the tests have run and all their334 * Configuration methods have been called.335 * 336 * @param context context for the test run337 */338 @Override339 public void onFinish(ITestContext context) {340 synchronized (testListeners) {341 for (ITestListener testListener : testListeners) {342 testListener.onFinish(context);343 }344 }345 }346 /**347 * [IMethodInterceptor]348 * Invoked to enable alteration of the list of test methods that TestNG is about to run.349 * 350 * @param methods list of test methods.351 * @param context test context.352 * @return the list of test methods to run.353 */354 @Override355 public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {356 synchronized (methodInterceptors) {357 for (IMethodInterceptor interceptor : methodInterceptors) {358 methods = interceptor.intercept(methods, context);359 }360 }361 return methods;362 }363 /**364 * [IClassListener]365 * Invoked after the test class is instantiated and before366 * {@link org.testng.annotations.BeforeClass @BeforeClass} 367 * configuration methods are called.368 * 369 * @param testClass TestNG representation for the current test class370 */371 @Override372 public void onBeforeClass(ITestClass testClass) {373 synchronized (classListeners) {374 for (IClassListener classListener : Lists.reverse(classListeners)) {375 classListener.onBeforeClass(testClass);376 }377 }378 }379 /**380 * [IClassListener]381 * Invoked after all of the test methods of the test class have been invoked382 * and before {@link org.testng.annotations.AfterClass @AfterClass}383 * configuration methods are called.384 * 385 * @param testClass TestNG representation for the current test class386 */387 @Override388 public void onAfterClass(ITestClass testClass) {389 synchronized (classListeners) {390 for (IClassListener classListener : classListeners) {391 classListener.onAfterClass(testClass);392 }393 }394 }395 396 /**397 * Get reference to an instance of the specified listener type.398 * 399 * @param <T> listener type400 * @param result TestNG test result object401 * @param listenerType listener type402 * @return optional listener instance403 */404 public static <T extends ITestNGListener> Optional<T>405 getAttachedListener(ITestResult result, Class<T> listenerType) {406 407 Objects.requireNonNull(result, "[result] must be non-null");408 return getAttachedListener(result.getTestContext(), listenerType);409 }410 411 /**412 * Get reference to an instance of the specified listener type.413 * 414 * @param <T> listener type415 * @param context TestNG test context object416 * @param listenerType listener type417 * @return optional listener instance418 */419 public static <T extends ITestNGListener> Optional<T>420 getAttachedListener(ITestContext context, Class<T> listenerType) {421 422 Objects.requireNonNull(context, "[context] must be non-null");423 return getAttachedListener(context.getSuite(), listenerType);424 }425 426 /**427 * Get reference to an instance of the specified listener type.428 * 429 * @param <T> listener type430 * @param suite TestNG suite object431 * @param listenerType listener type432 * @return optional listener instance433 */434 public static <T extends ITestNGListener> Optional<T>435 getAttachedListener(ISuite suite, Class<T> listenerType) {436 437 Objects.requireNonNull(suite, "[suite] must be non-null");438 Objects.requireNonNull(listenerType, "[listenerType] must be non-null");439 ListenerChain chain = (ListenerChain) suite.getAttribute(LISTENER_CHAIN);440 Objects.requireNonNull(chain, "Specified suite has no ListenerChain");441 return chain.getAttachedListener(listenerType);442 }443 /**444 * Get reference to an instance of the specified listener type.445 * 446 * @param <T> listener type447 * @param listenerType listener type448 * @return optional listener instance449 */450 @SuppressWarnings("unchecked")451 public <T extends ITestNGListener> Optional<T> getAttachedListener(Class<T> listenerType) {452 for (ITestNGListener listener : listeners) {453 if (listener.getClass() == listenerType) {454 return Optional.of((T) listener);455 }456 }457 return Optional.absent();458 }459 /**460 * Attach linked listeners that are active on the test class that contains the specified test method.461 * 462 * @param testMethod test method463 */464 protected void attachListeners(Method testMethod) {465 if (testMethod != null) {466 processLinkedListeners(testMethod.getDeclaringClass());467 }468 }469 470 /**471 * Attach linked listeners that are active on the test class defined by the specified test context. Note that only472 * one of the three parameters testClass, testCtor and testMethod will be non-null.473 * 474 * @param testClass If the annotation was found on a class, this parameter represents this class (null otherwise).475 * @param testCtor If the annotation was found on a constructor, this parameter represents this constructor (null476 * otherwise).477 * @param testMethod If the annotation was found on a method, this parameter represents this method (null478 * otherwise).479 */480 protected void attachListeners(Class<?> testClass, Constructor<?> testCtor, Method testMethod) {481 if (testClass != null) {482 processLinkedListeners(testClass);483 } else if (testCtor != null) {484 processLinkedListeners(testCtor.getDeclaringClass());485 } else if (testMethod != null) {486 processLinkedListeners(testMethod.getDeclaringClass());487 }488 }489 490 /**491 * Attach linked listeners that are active on the specified test class.492 * 493 * @param testClass test class494 */495 protected void attachListeners(Class<?> testClass) {496 if (testClass != null) {497 processLinkedListeners(testClass);498 }499 }500 501 /**502 * Process the {@link LinkedListeners} annotation of the specified test class.503 * 504 * @param testClass test class505 */506 protected void processLinkedListeners(Class<?> testClass) {507 Objects.requireNonNull(testClass, "[testClass] must be non-null");508 509 LinkedListeners annotation = testClass.getAnnotation(LinkedListeners.class);510 if (null != annotation) {511 Class<?> markedClass = getMarkedClass(testClass);512 if ( ! markedClasses.contains(markedClass)) {513 markedClasses.add(markedClass);514 for (Class<? extends ITestNGListener> listener : annotation.value()) {515 attachListener(listener, null);516 }517 }518 }519 }520 521 protected void initialize() {522 listeners = new ArrayList<>();523 annotationXformers = new ArrayList<>();524 executionListeners = new ArrayList<>();525 suiteListeners = new ArrayList<>();526 configListeners = new ArrayList<>();527 methodListeners = new ArrayList<>();528 testListeners = new ArrayList<>();529 methodInterceptors = new ArrayList<>();530 classListeners = new ArrayList<>();531 }532 533 /**534 * Wrap the current listener chain with an instance of the specified listener class.535 * <p>536 * <b>NOTE</b>: The order in which listener methods are invoked is determined by the537 * order in which listener objects are added to the chain. Listener <i>before</i> methods538 * are invoked in last-added-first-called order. Listener <i>after</i> methods are invoked539 * in first-added-first-called order.<br>540 * <b>NOTE</b>: Only one instance of any given listener class will be included in the chain.541 * 542 * @param listenerTyp listener class to add to the chain (may be 'null')543 * @param listenerObj listener object to add to the chain (may be 'null')544 */545 protected void attachListener(Class<? extends ITestNGListener> listenerTyp, ITestNGListener listenerObj) { //NOSONAR546 Class<? extends ITestNGListener> type;547 ITestNGListener object;548 549 if ((listenerTyp == null) && (listenerObj == null)) {550 throw new IllegalArgumentException("Neither [listenerTyp] nor [listenerObj] was specified");551 } else if (listenerObj != null) {552 object = listenerObj;553 type = listenerObj.getClass();554 } else {555 object = null;556 type = listenerTyp;557 }558 559 if ( ! listenerSet.contains(type)) { //NOSONAR560 listenerSet.add(type);561 562 if (object == null) {563 try {564 object = type.getDeclaredConstructor().newInstance();565 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException566 | InvocationTargetException | NoSuchMethodException | SecurityException e) {567 throw new RuntimeException("Unable to instantiate listener: " + type.getName(), e);568 }569 }570 571 synchronized(listeners) {572 listeners.add(object);573 }574 575 bucketizeListener(object);576 }577 }578 protected void bucketizeListener(ITestNGListener object) {579 if (object instanceof IExecutionListener) {580 synchronized(executionListeners) {581 executionListeners.add((IExecutionListener) object);582 }583 }584 585 if (object instanceof ISuiteListener) {586 synchronized(suiteListeners) {587 suiteListeners.add((ISuiteListener) object);588 }589 }590 591 if (object instanceof ITestListener) {592 synchronized(testListeners) {593 testListeners.add((ITestListener) object);594 }595 }596 597 if (object instanceof IMethodInterceptor) {598 synchronized(methodInterceptors) {599 methodInterceptors.add((IMethodInterceptor) object);600 }601 }602 603 if (object instanceof IClassListener) {604 synchronized(classListeners) {605 classListeners.add((IClassListener) object);606 }607 }608 }609 610 /**611 * Get the class in the hierarchy of the specified test class that declares the {@link LinkedListeners} annotation.612 * 613 * @param testClass test class to be evaluated614 * @return class that declares the {@link LinkedListeners} annotation; {@code null} if annotation isn't found615 */616 private static Class<?> getMarkedClass(Class<?> testClass) {617 for (Class<?> thisClass = testClass; thisClass != null; thisClass = thisClass.getSuperclass()) {618 for (Annotation annotation : thisClass.getDeclaredAnnotations()) {619 if (annotation.annotationType().isAssignableFrom(LinkedListeners.class)) {620 return thisClass;621 }622 }623 }624 return null;625 }626}...