001package arez;
002
003import java.lang.reflect.Field;
004import javax.annotation.Nonnull;
005import javax.annotation.Nullable;
006
007/**
008 * Utility class for interacting with Arez config settings in tests.
009 */
010@SuppressWarnings( "WeakerAccess" )
011@GwtIncompatible
012public final class ArezTestUtil
013{
014  private ArezTestUtil()
015  {
016  }
017
018  /**
019   * Interface to intercept log messages emitted by Arez runtime.
020   */
021  public interface Logger
022  {
023    void log( @Nonnull String message, @Nullable Throwable throwable );
024  }
025
026  /**
027   * Reset the state of Arez config to either production or development state.
028   *
029   * @param productionMode true to set it to production mode configuration, false to set it to development mode config.
030   */
031  public static void resetConfig( final boolean productionMode )
032  {
033    if ( ArezConfig.isProductionEnvironment() )
034    {
035      /*
036       * This should really never happen but if it does add assertion (so code stops in debugger) or
037       * failing that throw an exception.
038       */
039      assert ArezConfig.isDevelopmentEnvironment();
040      throw new IllegalStateException( "Unable to reset config as Arez is in production mode" );
041    }
042
043    if ( productionMode )
044    {
045      disableNames();
046      disableVerify();
047      disablePropertyIntrospectors();
048      noEnforceTransactionType();
049      disableSpies();
050      makeCollectionPropertiesModifiable();
051      disableNativeComponents();
052      disableRegistries();
053      noCheckInvariants();
054      noCheckApiInvariants();
055    }
056    else
057    {
058      enableNames();
059      enableVerify();
060      enablePropertyIntrospectors();
061      enforceTransactionType();
062      enableSpies();
063      makeCollectionPropertiesUnmodifiable();
064      enableNativeComponents();
065      enableRegistries();
066      checkInvariants();
067      checkApiInvariants();
068    }
069    enableObserverErrorHandlers();
070    enableReferences();
071    enableTaskInterceptor();
072    purgeTasksWhenRunawayDetected();
073    disableZones();
074    resetState();
075  }
076
077  /**
078   * Reset the state of Arez.
079   * This occasionally needs to be invoked after changing configuration settings in tests.
080   */
081  private static void resetState()
082  {
083    setLogger( null );
084    Transaction.setTransaction( null );
085    ZoneHolder.reset();
086    ArezContextHolder.reset();
087  }
088
089  /**
090   * Specify logger to use to capture logging in tests
091   *
092   * @param logger the logger.
093   */
094  public static void setLogger( @Nullable final Logger logger )
095  {
096    if ( ArezConfig.isProductionEnvironment() )
097    {
098      /*
099       * This should really never happen but if it does add assertion (so code stops in debugger) or
100       * failing that throw an exception.
101       */
102      assert ArezConfig.isDevelopmentEnvironment();
103      throw new IllegalStateException( "Unable to call ArezTestUtil.setLogger() as Arez is in production mode" );
104    }
105
106    final ArezLogger.ProxyLogger proxyLogger = (ArezLogger.ProxyLogger) ArezLogger.getLogger();
107    proxyLogger.setLogger( null == logger ? null : logger::log );
108  }
109
110  /**
111   * Set the `arez.enable_names` setting to true.
112   */
113  public static void enableNames()
114  {
115    setEnableNames( true );
116  }
117
118  /**
119   * Set the `arez.enable_names` setting to false.
120   */
121  public static void disableNames()
122  {
123    setEnableNames( false );
124  }
125
126  /**
127   * Configure the `arez.enable_names` setting.
128   *
129   * @param value the setting.
130   */
131  private static void setEnableNames( final boolean value )
132  {
133    setConstant( "ENABLE_NAMES", value );
134  }
135
136  /**
137   * Set the `arez.enable_references` setting to true.
138   */
139  public static void enableReferences()
140  {
141    setEnableReferences( true );
142  }
143
144  /**
145   * Set the `arez.enable_references` setting to false.
146   */
147  public static void disableReferences()
148  {
149    setEnableReferences( false );
150  }
151
152  /**
153   * Configure the `arez.enable_references` setting.
154   *
155   * @param value the setting.
156   */
157  private static void setEnableReferences( final boolean value )
158  {
159    setConstant( "ENABLE_REFERENCES", value );
160  }
161
162  /**
163   * Set the `arez.enable_verify` setting to true.
164   */
165  public static void enableVerify()
166  {
167    setEnableVerify( true );
168  }
169
170  /**
171   * Set the `arez.enable_verify` setting to false.
172   */
173  public static void disableVerify()
174  {
175    setEnableVerify( false );
176  }
177
178  /**
179   * Configure the `arez.enable_verify` setting.
180   *
181   * @param value the setting.
182   */
183  private static void setEnableVerify( final boolean value )
184  {
185    setConstant( "ENABLE_VERIFY", value );
186  }
187
188  /**
189   * Set the `arez.enable_property_introspection` setting to true.
190   */
191  public static void enablePropertyIntrospectors()
192  {
193    setPropertyIntrospection( true );
194  }
195
196  /**
197   * Set the `arez.enable_property_introspection` setting to false.
198   */
199  public static void disablePropertyIntrospectors()
200  {
201    setPropertyIntrospection( false );
202  }
203
204  /**
205   * Configure the `arez.enable_property_introspection` setting.
206   *
207   * @param value the setting.
208   */
209  private static void setPropertyIntrospection( final boolean value )
210  {
211    setConstant( "ENABLE_PROPERTY_INTROSPECTION", value );
212  }
213
214  /**
215   * Set the `arez.purge_tasks_when_runaway_detected` setting to true.
216   */
217  public static void purgeTasksWhenRunawayDetected()
218  {
219    setPurgeTasksWhenRunawayDetected( true );
220  }
221
222  /**
223   * Set the `arez.purge_tasks_when_runaway_detected` setting to false.
224   */
225  public static void noPurgeTasksWhenRunawayDetected()
226  {
227    setPurgeTasksWhenRunawayDetected( false );
228  }
229
230  /**
231   * Configure the `arez.purge_tasks_when_runaway_detected` setting.
232   *
233   * @param value the setting.
234   */
235  private static void setPurgeTasksWhenRunawayDetected( final boolean value )
236  {
237    setConstant( "PURGE_ON_RUNAWAY", value );
238  }
239
240  /**
241   * Set the `arez.enforce_transaction_type` setting to true.
242   */
243  public static void enforceTransactionType()
244  {
245    setEnforceTransactionType( true );
246  }
247
248  /**
249   * Set the `arez.enforce_transaction_type` setting to false.
250   */
251  public static void noEnforceTransactionType()
252  {
253    setEnforceTransactionType( false );
254  }
255
256  /**
257   * Configure the `arez.enforce_transaction_type` setting.
258   *
259   * @param value the setting.
260   */
261  private static void setEnforceTransactionType( final boolean value )
262  {
263    setConstant( "ENFORCE_TRANSACTION_TYPE", value );
264  }
265
266  /**
267   * Set the `arez.enable_spies` setting to true.
268   */
269  public static void enableSpies()
270  {
271    setEnableSpies( true );
272    resetState();
273  }
274
275  /**
276   * Set the `arez.enable_spies` setting to false.
277   */
278  public static void disableSpies()
279  {
280    setEnableSpies( false );
281    resetState();
282  }
283
284  /**
285   * Configure the "arez.enable_spies" setting.
286   *
287   * @param value the setting.
288   */
289  private static void setEnableSpies( final boolean value )
290  {
291    setConstant( "ENABLE_SPIES", value );
292  }
293
294  /**
295   * Set the `arez.enable_zones` setting to true.
296   * This will result in the Arez state being reset to align with this setting. The
297   * normal practice is to invoke this at the start of a test.
298   */
299  public static void enableZones()
300  {
301    setEnableZones( true );
302    resetState();
303  }
304
305  /**
306   * Set the `arez.enable_zones` setting to false.
307   * This will result in the Arez state being reset to align with this setting. The
308   * normal practice is to invoke this at the start of a test.
309   */
310  public static void disableZones()
311  {
312    setEnableZones( false );
313    resetState();
314  }
315
316  /**
317   * Configure the `arez.enable_zones` setting.
318   *
319   * @param value the setting.
320   */
321  private static void setEnableZones( final boolean value )
322  {
323    setConstant( "ENABLE_ZONES", value );
324  }
325
326  /**
327   * Set the `arez.collections_properties_unmodifiable` setting to true.
328   */
329  public static void makeCollectionPropertiesModifiable()
330  {
331    setCollectionPropertiesUnmodifiable( false );
332  }
333
334  /**
335   * Set the `arez.collections_properties_unmodifiable` setting to false.
336   */
337  public static void makeCollectionPropertiesUnmodifiable()
338  {
339    setCollectionPropertiesUnmodifiable( true );
340  }
341
342  /**
343   * Configure the `arez.collections_properties_unmodifiable` setting.
344   *
345   * @param value the setting.
346   */
347  private static void setCollectionPropertiesUnmodifiable( final boolean value )
348  {
349    setConstant( "COLLECTION_PROPERTIES_UNMODIFIABLE", value );
350  }
351
352  /**
353   * Set the `arez.enable_native_components` setting to true.
354   * This will result in the Arez state being reset to align with this setting. The
355   * normal practice is to invoke this at the start of a test.
356   */
357  public static void enableNativeComponents()
358  {
359    setEnableNativeComponents( true );
360    resetState();
361  }
362
363  /**
364   * Set the `arez.enable_native_components` setting to false.
365   * This will result in the Arez state being reset to align with this setting. The
366   * normal practice is to invoke this at the start of a test.
367   */
368  public static void disableNativeComponents()
369  {
370    setEnableNativeComponents( false );
371    resetState();
372  }
373
374  /**
375   * Configure the `arez.enable_native_components` setting.
376   *
377   * @param value the setting.
378   */
379  private static void setEnableNativeComponents( final boolean value )
380  {
381    setConstant( "ENABLE_NATIVE_COMPONENTS", value );
382  }
383
384  /**
385   * Set the `arez.enable_registries` setting to true.
386   * This will result in the Arez state being reset to align with this setting. The
387   * normal practice is to invoke this at the start of a test.
388   */
389  public static void enableRegistries()
390  {
391    setEnableRegistries( true );
392    resetState();
393  }
394
395  /**
396   * Set the `arez.enable_registries` setting to false.
397   * This will result in the Arez state being reset to align with this setting. The
398   * normal practice is to invoke this at the start of a test.
399   */
400  public static void disableRegistries()
401  {
402    setEnableRegistries( false );
403    resetState();
404  }
405
406  /**
407   * Configure the `arez.enable_registries` setting.
408   *
409   * @param value the setting.
410   */
411  private static void setEnableRegistries( final boolean value )
412  {
413    setConstant( "ENABLE_REGISTRIES", value );
414  }
415
416  /**
417   * Set the `arez.enable_task_interceptor` setting to true.
418   */
419  public static void enableTaskInterceptor()
420  {
421    setEnableTaskInterceptor( true );
422    resetState();
423  }
424
425  /**
426   * Set the `arez.enable_task_interceptor` setting to false.
427   */
428  public static void disableTaskInterceptor()
429  {
430    setEnableTaskInterceptor( false );
431    resetState();
432  }
433
434  /**
435   * Configure the "arez.enable_task_interceptor" setting.
436   *
437   * @param value the setting.
438   */
439  private static void setEnableTaskInterceptor( final boolean value )
440  {
441    setConstant( "ENABLE_TASK_INTERCEPTOR", value );
442  }
443
444  /**
445   * Set the `arez.enable_observer_error_handlers` setting to true.
446   */
447  public static void enableObserverErrorHandlers()
448  {
449    setEnableObserverErrorHandlers( true );
450  }
451
452  /**
453   * Set the `arez.enable_observer_error_handlers` setting to false.
454   */
455  public static void disableObserverErrorHandlers()
456  {
457    setEnableObserverErrorHandlers( false );
458  }
459
460  /**
461   * Configure the `arez.enable_observer_error_handlers` setting.
462   *
463   * @param value the setting.
464   */
465  private static void setEnableObserverErrorHandlers( final boolean value )
466  {
467    setConstant( "ENABLE_OBSERVER_ERROR_HANDLERS", value );
468  }
469
470  /**
471   * Set the `arez.check_invariants` setting to true.
472   */
473  public static void checkInvariants()
474  {
475    setCheckInvariants( true );
476  }
477
478  /**
479   * Set the `arez.check_invariants` setting to false.
480   */
481  public static void noCheckInvariants()
482  {
483    setCheckInvariants( false );
484  }
485
486  /**
487   * Configure the `arez.check_invariants` setting.
488   *
489   * @param checkInvariants the "check invariants" setting.
490   */
491  private static void setCheckInvariants( final boolean checkInvariants )
492  {
493    setConstant( "CHECK_INVARIANTS", checkInvariants );
494  }
495
496  /**
497   * Set the `arez.check_api_invariants` setting to true.
498   */
499  public static void checkApiInvariants()
500  {
501    setCheckApiInvariants( true );
502  }
503
504  /**
505   * Set the `arez.check_api_invariants` setting to false.
506   */
507  public static void noCheckApiInvariants()
508  {
509    setCheckApiInvariants( false );
510  }
511
512  /**
513   * Configure the `arez.check_api_invariants` setting.
514   *
515   * @param checkApiInvariants the "check invariants" setting.
516   */
517  private static void setCheckApiInvariants( final boolean checkApiInvariants )
518  {
519    setConstant( "CHECK_API_INVARIANTS", checkApiInvariants );
520  }
521
522  /**
523   * Set the specified field name on ArezConfig.
524   */
525  @SuppressWarnings( "NonJREEmulationClassesInClientCode" )
526  private static void setConstant( @Nonnull final String fieldName, final boolean value )
527  {
528    if ( ArezConfig.isProductionEnvironment() )
529    {
530      /*
531       * This should really never happen but if it does add assertion (so code stops in debugger) or
532       * failing that throw an exception.
533       */
534      assert ArezConfig.isDevelopmentEnvironment();
535      throw new IllegalStateException( "Unable to change constant " + fieldName + " as Arez is in production mode" );
536    }
537    else
538    {
539      try
540      {
541        final Field field = ArezConfig.class.getDeclaredField( fieldName );
542        field.setAccessible( true );
543        field.set( null, value );
544      }
545      catch ( final NoSuchFieldException | IllegalAccessException e )
546      {
547        throw new IllegalStateException( "Unable to change constant " + fieldName, e );
548      }
549    }
550  }
551}