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}