001package arez.testng; 002 003import arez.ActionFlags; 004import arez.Arez; 005import arez.ArezTestUtil; 006import java.lang.reflect.Field; 007import java.lang.reflect.Method; 008import java.util.List; 009import javax.annotation.Nonnull; 010import javax.annotation.Nullable; 011import org.realityforge.braincheck.BrainCheckTestUtil; 012import org.testng.IHookCallBack; 013import org.testng.IHookable; 014import org.testng.ITestResult; 015import static org.testng.Assert.*; 016 017public final class ArezTestHook 018 implements IHookable 019{ 020 @Override 021 public void run( final IHookCallBack icb, final ITestResult result ) 022 { 023 final Method method = result.getMethod().getConstructorOrMethod().getMethod(); 024 final Object instance = result.getInstance(); 025 026 final boolean collectObserverErrors = null != method.getAnnotation( CollectObserverErrors.class ); 027 028 final ObserverErrorCollector collector; 029 if ( collectObserverErrors ) 030 { 031 linkObserverErrorCollectors( instance ); 032 collector = null; 033 } 034 else 035 { 036 collector = new ObserverErrorCollector( true ); 037 linkObserverErrorCollector( collector ); 038 } 039 040 final ActionWrapper actionWrapper = getActionWrapperAnnotation( method ); 041 if ( null == actionWrapper || !actionWrapper.enable() ) 042 { 043 icb.runTestMethod( result ); 044 } 045 else 046 { 047 Arez.context().safeAction( result.getName(), 048 () -> icb.runTestMethod( result ), 049 ActionFlags.NO_VERIFY_ACTION_REQUIRED ); 050 } 051 052 if ( null != collector ) 053 { 054 final List<String> observerErrors = collector.getObserverErrors(); 055 if ( !observerErrors.isEmpty() ) 056 { 057 fail( "Unexpected Observer Errors: " + String.join( "\n", observerErrors ) ); 058 } 059 } 060 } 061 062 @Nullable 063 private ActionWrapper getActionWrapperAnnotation( @Nonnull final Method method ) 064 { 065 final ActionWrapper annotation = method.getAnnotation( ActionWrapper.class ); 066 if ( null != annotation ) 067 { 068 return annotation; 069 } 070 else 071 { 072 return getActionWrapperAnnotation( method.getDeclaringClass() ); 073 } 074 } 075 076 @Nullable 077 private ActionWrapper getActionWrapperAnnotation( @Nonnull final Class<?> type ) 078 { 079 final ActionWrapper annotation = type.getAnnotation( ActionWrapper.class ); 080 if ( null != annotation ) 081 { 082 return annotation; 083 } 084 else 085 { 086 final Class<?> superclass = type.getSuperclass(); 087 return null == superclass ? null : getActionWrapperAnnotation( superclass ); 088 } 089 } 090 091 private void linkObserverErrorCollectors( @Nonnull final Object instance ) 092 { 093 for ( final Field field : instance.getClass().getDeclaredFields() ) 094 { 095 if ( field.getType().equals( ObserverErrorCollector.class ) ) 096 { 097 field.setAccessible( true ); 098 try 099 { 100 final ObserverErrorCollector collector = (ObserverErrorCollector) field.get( instance ); 101 collector.clear(); 102 linkObserverErrorCollector( collector ); 103 } 104 catch ( final IllegalAccessException ignored ) 105 { 106 } 107 } 108 } 109 } 110 111 private void linkObserverErrorCollector( @Nonnull final ObserverErrorCollector collector ) 112 { 113 Arez.context().addObserverErrorHandler( collector::onObserverError ); 114 } 115}